| name | interactor-agents |
| description | Create LLM-powered AI assistants with tools and data sources through Interactor. Use when building conversational AI, chatbots, tool-calling assistants, or agents that need to query databases and external APIs. |
| author | Interactor Integration Guide |
Interactor AI Agents Skill
Build LLM-powered assistants that can have conversations, use tools, and access your data sources.
When to Use
- Conversational AI: Building chat interfaces with AI assistants
- Tool-Calling Agents: Creating assistants that can invoke custom functions
- Data-Connected AI: Connecting AI to databases for natural language queries
- Customer Support Bots: Building support assistants with domain knowledge
- Internal Tools: Creating AI assistants for internal operations
Prerequisites
- Interactor authentication configured (see
interactor-auth skill)
- Understanding of LLM concepts (prompts, tools, context)
- Webhook endpoint for tool callbacks (optional, for custom tools)
- Database with network access from Interactor (optional, for data sources)
jq command-line tool (for bash examples)
Overview
The AI Agents system consists of:
| Component | Description |
|---|
| Assistants | Configured AI agents with specific behaviors and capabilities |
| Rooms | Chat sessions between users and assistants |
| Messages | Individual messages in a conversation |
| Tools | Custom functions that assistants can invoke |
| Data Sources | Databases and APIs that assistants can query |
Quick Start
The typical implementation flow:
1. Create an Assistant → Define behavior with instructions and model config
↓
2. Register Tools → (Optional) Add custom functions the assistant can call
↓
3. Connect Data Sources → (Optional) Connect databases for natural language queries
↓
4. Create a Room → Start a conversation session for a user
↓
5. Send/Receive Messages → Exchange messages, handle tool calls
↓
6. Close Room → End the conversation when complete
Minimal Example:
Prerequisites: This example requires jq for JSON parsing and a valid $TOKEN.
See interactor-auth skill for authentication setup.
ASSISTANT_ID=$(curl -s -X POST https://core.interactor.com/api/v1/agents/assistants \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "helper", "title": "Helper", "instructions": "You are a helpful assistant."}' \
| jq -r '.data.id')
echo "Created assistant: $ASSISTANT_ID"
ROOM_ID=$(curl -s -X POST https://core.interactor.com/api/v1/agents/$ASSISTANT_ID/rooms \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"namespace": "user_123"}' \
| jq -r '.data.id')
echo "Created room: $ROOM_ID"
curl -X POST https://core.interactor.com/api/v1/agents/rooms/$ROOM_ID/messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Hello!", "role": "user"}'
Instructions
API Version Note: This skill documents the standard AI Agents API. Some operations
(DELETE endpoints, advanced pagination options) may vary by Interactor version.
Always verify endpoint availability against your specific API documentation or test
in a development environment first.
Step 1: Create an Assistant
curl -X POST https://core.interactor.com/api/v1/agents/assistants \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "support_assistant",
"title": "Support Assistant",
"description": "Helps users with support questions",
"model_config": {
"provider": "openai",
"model": "gpt-4o",
"temperature": 0.7
},
"instructions": "You are a helpful support assistant. Be concise and friendly. Always try to resolve issues on the first response.",
"enabled_tools": ["search_knowledge_base", "create_ticket"]
}'
Response:
{
"data": {
"id": "asst_abc",
"name": "support_assistant",
"title": "Support Assistant",
"description": "Helps users with support questions",
"model_config": {
"provider": "openai",
"model": "gpt-4o",
"temperature": 0.7
},
"enabled_tools": ["search_knowledge_base", "create_ticket"],
"created_at": "2026-01-20T12:00:00Z"
}
}
Assistant Configuration Options
| Field | Type | Required | Description |
|---|
name | string | Yes | Unique identifier (lowercase, underscores only) |
title | string | Yes | Display name for users |
description | string | No | What the assistant does |
model_config.provider | string | No | openai (default: openai) |
model_config.model | string | No | Model identifier (default: gpt-4o) |
model_config.temperature | number | No | Response randomness 0.0-1.0 (default: 0.7) |
instructions | string | Yes | System prompt defining behavior |
enabled_tools | array | No | Tool names the assistant can use |
List Assistants
curl https://core.interactor.com/api/v1/agents/assistants \
-H "Authorization: Bearer <token>"
Get Assistant
curl https://core.interactor.com/api/v1/agents/assistants/asst_abc \
-H "Authorization: Bearer <token>"
Update Assistant
curl -X PUT https://core.interactor.com/api/v1/agents/assistants/asst_abc \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"instructions": "Updated instructions with more detail...",
"model_config": {
"temperature": 0.5
}
}'
Delete Assistant
curl -X DELETE https://core.interactor.com/api/v1/agents/assistants/asst_abc \
-H "Authorization: Bearer <token>"
Chat Rooms
Rooms are conversations between a user and an assistant.
Create a Room
curl -X POST https://core.interactor.com/api/v1/agents/asst_abc/rooms \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "user_123",
"metadata": {
"user_name": "John",
"context": "billing_question",
"plan": "premium"
}
}'
Response:
{
"data": {
"id": "room_xyz",
"assistant_id": "asst_abc",
"namespace": "user_123",
"status": "active",
"metadata": {
"user_name": "John",
"context": "billing_question",
"plan": "premium"
},
"created_at": "2026-01-20T12:00:00Z"
}
}
List Rooms
curl https://core.interactor.com/api/v1/agents/rooms \
-H "Authorization: Bearer <token>"
Query Parameters:
| Parameter | Type | Description |
|---|
namespace | string | Filter by namespace |
assistant_id | string | Filter by assistant |
status | string | active or closed |
Example - List active rooms for a user:
curl "https://core.interactor.com/api/v1/agents/rooms?namespace=user_123&status=active" \
-H "Authorization: Bearer <token>"
Get Room
curl https://core.interactor.com/api/v1/agents/rooms/room_xyz \
-H "Authorization: Bearer <token>"
Response includes conversation history:
{
"data": {
"id": "room_xyz",
"assistant_id": "asst_abc",
"status": "active",
"metadata": {...},
"message_count": 5,
"created_at": "2026-01-20T12:00:00Z",
"last_message_at": "2026-01-20T12:05:00Z"
}
}
Close Room
curl -X POST https://core.interactor.com/api/v1/agents/rooms/room_xyz/close \
-H "Authorization: Bearer <token>"
Messages
Send a Message
curl -X POST https://core.interactor.com/api/v1/agents/rooms/room_xyz/messages \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"content": "How do I update my billing information?",
"role": "user"
}'
Response:
{
"data": {
"id": "msg_123",
"role": "user",
"content": "How do I update my billing information?",
"created_at": "2026-01-20T12:00:00Z"
}
}
The assistant's response is generated asynchronously. Use streaming or webhooks to receive it.
List Messages
curl https://core.interactor.com/api/v1/agents/rooms/room_xyz/messages \
-H "Authorization: Bearer <token>"
Query Parameters:
| Parameter | Type | Description |
|---|
limit | integer | Max messages to return (default: 50, max: 100) |
cursor | string | Opaque cursor for pagination (from next_cursor) |
order | string | Sort order: asc (oldest first) or desc (newest first, default) |
Response:
{
"data": {
"messages": [
{
"id": "msg_123",
"role": "user",
"content": "How do I update my billing information?",
"created_at": "2026-01-20T12:00:00Z"
},
{
"id": "msg_124",
"role": "assistant",
"content": "To update your billing information, go to Settings > Billing...",
"tool_calls": [],
"created_at": "2026-01-20T12:00:05Z"
}
],
"has_more": true,
"next_cursor":
Pagination Pattern
The API uses cursor-based pagination for stable, consistent results:
async function getAllMessages(roomId: string): Promise<Message[]> {
const allMessages: Message[] = [];
let cursor: string | undefined;
do {
const params = new URLSearchParams({ limit: '100' });
if (cursor) params.set('cursor', cursor);
const response = await fetch(
`https://core.interactor.com/api/v1/agents/rooms/${roomId}/messages?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const { data } = await response.json();
allMessages.push(...data.messages);
cursor = data.has_more ? data.next_cursor : undefined;
} while (cursor);
return allMessages;
}
Pagination Best Practices:
- Always use
next_cursor from the response; never construct cursors manually
- Cursors are opaque and may change format between API versions
- Cursors expire after 24 hours; for long-running jobs, re-fetch from the beginning
- Results are stable within a cursor session (new messages won't appear mid-pagination)
Message Roles
| Role | Description |
|---|
user | Message from the user |
assistant | Response from the AI assistant |
system | System message (internal use) |
tool | Tool execution result |
Real-Time Responses
Assistant responses are generated asynchronously after you send a user message. To receive responses in real-time, you have three options:
API Note: Verify streaming endpoint availability with your Interactor version.
The SSE endpoint pattern shown below (/rooms/{id}/stream) is a common convention
but may differ in your deployment. Check your API documentation or contact support.
Option 1: Server-Sent Events (SSE)
Subscribe to room events for streaming tokens as they're generated.
Note: The standard browser EventSource API doesn't support custom headers.
Use the eventsource npm package for Node.js, or pass the token as a query parameter
if your API supports it.
Node.js with eventsource package:
import EventSource from 'eventsource';
const eventSource = new EventSource(
`https://core.interactor.com/api/v1/agents/rooms/${roomId}/stream`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'token') {
process.stdout.write(data.token);
} else if (data.type === 'message_complete') {
console.log('\nComplete:', data.message);
} else if (data.type === 'tool_call') {
console.log('Tool call:', data.tool_name);
}
};
eventSource.onerror = (error) => {
console.(, error);
eventSource.();
};
Browser with Fetch API (alternative):
async function streamMessages(roomId: string, token: string) {
const response = await fetch(
`https://core.interactor.com/api/v1/agents/rooms/${roomId}/stream`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (reader) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
console.log('Received:', data);
}
}
}
}
Option 2: Webhooks
Configure a webhook to receive agent.room.message events at your endpoint. See the interactor-webhooks skill for complete webhook setup and payload handling.
Option 3: Polling (Not Recommended)
Poll the messages endpoint for new messages. Use only as a fallback:
async function pollForResponse(roomId: string, lastKnownMessageId: string) {
const maxAttempts = 30;
const delayMs = 1000;
for (let i = 0; i < maxAttempts; i++) {
const { messages } = await getMessages(roomId, { limit: 10 });
const newMessages = messages.filter(m =>
m.role === 'assistant' && m.id !== lastKnownMessageId
);
if (newMessages.length > 0) return newMessages[0];
await new Promise(resolve => setTimeout(resolve, delayMs));
}
throw new Error('Response timeout');
}
Tools
Tools are custom functions that assistants can invoke during conversations.
Register a Tool
curl -X POST https://core.interactor.com/api/v1/tools \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "search_products",
"description": "Search the product catalog by query and optional category",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query for product names or descriptions"
},
"category": {
"type": "string",
"description": "Product category to filter by",
"enum": ["electronics", "clothing", "home", "sports"]
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return",
"default": 10
}
},
"required": ["query"]
},
"callback_url": "https://yourapp.com/api/tools/search_products",
"callback_secret": "your_webhook_secret_here"
}'
Response:
{
"data": {
"id": "tool_abc",
"name": "search_products",
"description": "Search the product catalog by query and optional category",
"created_at": "2026-01-20T12:00:00Z"
}
}
Tool Callback
When the assistant invokes your tool, Interactor POSTs to your callback_url:
Request Headers:
POST /api/tools/search_products HTTP/1.1
Content-Type: application/json
X-Interactor-Signature: sha256=abc123def456...
X-Interactor-Timestamp: 2026-01-20T12:00:00Z
Request Body:
{
"tool_name": "search_products",
"parameters": {
"query": "laptop",
"category": "electronics",
"max_results": 5
},
"execution_id": "exec_xyz",
"room_id": "room_xyz",
"assistant_id": "asst_abc",
"timestamp": "2026-01-20T12:00:00Z"
}
Security Note: The X-Interactor-Signature header contains an HMAC-SHA256 signature
of the raw request body, signed with your callback_secret. Always verify this signature
before processing the request.
Your response:
{
"result": {
"products": [
{
"id": "prod_1",
"name": "MacBook Pro 14\"",
"price": 1999,
"in_stock": true
},
{
"id": "prod_2",
"name": "Dell XPS 15",
"price": 1499,
"in_stock": true
}
],
"total_count": 2
}
}
The assistant will use this result to formulate its response.
Verify Tool Callback Signature
Tool callbacks include three security headers for verification:
| Header | Description |
|---|
X-Interactor-Signature | HMAC-SHA256 signature of the raw request body |
X-Interactor-Timestamp | ISO 8601 timestamp when request was signed |
X-Interactor-Request-Id | Unique request ID (same as execution_id in body) |
Security Requirements:
- Verify HMAC signature matches
- Check timestamp is within acceptable window (recommended: 300 seconds)
- Use
execution_id as idempotency key to prevent replay attacks
TypeScript (with replay protection):
import crypto from 'crypto';
interface VerificationResult {
valid: boolean;
error?: string;
}
function verifyToolCallback(
payload: string,
signature: string,
timestamp: string,
secret: string,
maxAgeSeconds: number = 300
): VerificationResult {
const requestTime = Date.parse(timestamp);
const now = Date.now();
const ageSeconds = Math.abs(now - requestTime) / 1000;
if (ageSeconds > maxAgeSeconds) {
return { valid: false, error: `Request too old: ${ageSeconds}s > ${maxAgeSeconds}s` };
}
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
signatureValid = crypto.(
.(signature.(, )),
.(expected)
);
(!signatureValid) {
{ : , : };
}
{ : };
}
app.(, express.({ : }), (req, res) => {
signature = req.[] ;
timestamp = req.[] ;
requestId = req.[] ;
payload = req..();
verification = (
payload,
signature,
timestamp,
process..!
);
(!verification.) {
.(, verification., { requestId });
res.().({ : verification. });
}
data = .(payload);
alreadyProcessed = (data.);
(alreadyProcessed) {
.(, data.);
res.().({ : (data.) });
}
result = (data.);
(data., result, * * * );
res.({ result });
});
(): <> {
;
}
(): <> {
;
}
(): <> {
}
Python (with replay protection):
import os
import hmac
import hashlib
import time
from datetime import datetime
from flask import Flask, request, jsonify
app = Flask(__name__)
def verify_tool_callback(
payload: bytes,
signature: str,
timestamp: str,
secret: str,
max_age_seconds: int = 300
) -> tuple[bool, str | None]:
"""
Verify callback with replay protection.
Returns (is_valid, error_message).
"""
try:
request_time = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
age_seconds = abs((datetime.now(request_time.tzinfo) - request_time).total_seconds())
if age_seconds > max_age_seconds:
return False, f'Request too old: {age_seconds}s > {max_age_seconds}s'
except ValueError as e:
return False, f'Invalid timestamp: {e}'
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
signature_value = signature.replace('sha256=', '')
if not hmac.compare_digest(signature_value, expected):
,
,
():
signature = request.headers.get(, )
timestamp = request.headers.get(, )
request_id = request.headers.get(, )
payload = request.get_data()
is_valid, error = verify_tool_callback(
payload,
signature,
timestamp,
os.environ[]
)
is_valid:
app.logger.error(, extra={: request_id})
jsonify({: error}),
data = request.get_json()
is_duplicate_execution(data[]):
jsonify({: get_cached_result(data[])}),
result = search_products(data[])
store_execution_result(data[], result)
jsonify({: result})
List Tools
curl https://core.interactor.com/api/v1/tools \
-H "Authorization: Bearer <token>"
Get Tool
curl https://core.interactor.com/api/v1/tools/tool_abc \
-H "Authorization: Bearer <token>"
Update Tool
curl -X PUT https://core.interactor.com/api/v1/tools/tool_abc \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated description with more detail",
"callback_url": "https://yourapp.com/api/v2/tools/search_products"
}'
Delete Tool
curl -X DELETE https://core.interactor.com/api/v1/tools/tool_abc \
-H "Authorization: Bearer <token>"
Webhook & Callback Contract
This section defines the canonical contract for all Interactor webhooks and tool callbacks.
Request Headers
All webhook/callback requests include these headers:
| Header | Format | Description |
|---|
X-Interactor-Signature | sha256=<hex> | HMAC-SHA256 of raw body |
X-Interactor-Timestamp | ISO 8601 | When request was signed |
X-Interactor-Request-Id | exec_<id> | Unique request identifier |
Content-Type | application/json | Always JSON |
Response Requirements
| Status | Meaning | Interactor Behavior |
|---|
200 | Success | Process result, no retry |
201 | Created | Same as 200 |
202 | Accepted | Async processing (see below) |
400 | Bad Request | Permanent failure, no retry |
401 | Unauthorized | Permanent failure, no retry |
404 | Not Found | Permanent failure, no retry |
429 | Rate Limited | Retry with backoff |
500 | Server Error | Retry with backoff |
502-504 | Gateway Error | Retry with backoff |
Expected Response Format:
{
"result": { ... }
}
Timeout Policy
| Operation | Timeout | Behavior on Timeout |
|---|
| Tool callback | 30 seconds | Retry with backoff |
| Webhook delivery | 10 seconds | Retry with backoff |
| Async callback (202) | 5 minutes | Poll for completion |
Important: Your callback must respond within the timeout window. For long-running
operations, return 202 Accepted with a status URL, and Interactor will poll for completion.
Retry Policy
When a callback fails (5xx, timeout, network error), Interactor retries with exponential backoff:
| Attempt | Delay | Cumulative Time |
|---|
| 1 | Immediate | 0s |
| 2 | 1s | 1s |
| 3 | 2s | 3s |
| 4 | 4s | 7s |
| 5 | 8s | 15s |
| 6 (final) | 16s | 31s |
After 6 failed attempts:
- Request is marked as permanently failed
tool.callback.failed event is emitted (if webhooks configured)
- Assistant receives an error and may retry or inform the user
Idempotency Requirements
You MUST implement idempotent handlers. Use execution_id as the idempotency key.
Idempotency Implementation:
import Redis from 'ioredis';
const redis = new Redis();
const IDEMPOTENCY_TTL = 7 * 24 * 60 * 60;
async function handleWithIdempotency(
executionId: string,
handler: () => Promise<any>
): Promise<{ result: any; isDuplicate: boolean }> {
const cacheKey = `idempotency:${executionId}`;
const cached = await redis.get(cacheKey);
if (cached) {
return { result: JSON.parse(cached), isDuplicate: true };
}
const result = await handler();
await redis.setex(cacheKey, IDEMPOTENCY_TTL, JSON.(result));
{ result, : };
}
Recommended idempotency window: 7 days minimum. This covers retry scenarios and
delayed processing.
Dead Letter Handling
If all retries are exhausted:
- Interactor emits a
tool.callback.dead_letter event (if webhooks configured)
- The failed request is logged in your Interactor dashboard
- You can manually retry from the dashboard or via API
Dead Letter Event Payload:
{
"event": "tool.callback.dead_letter",
"data": {
"execution_id": "exec_xyz",
"tool_name": "search_products",
"assistant_id": "asst_abc",
"room_id": "room_xyz",
"attempts": 6,
"last_error": "Connection timeout",
"first_attempt_at": "2026-01-20T12:00:00Z",
"last_attempt_at": "2026-01-20T12:00:31Z"
}
}
Streaming Contract
This section defines the canonical contract for Server-Sent Events (SSE) streaming.
Event Types
The streaming endpoint emits these event types:
| Event Type | Description | When Emitted |
|---|
token | Incremental response token | During response generation |
message_complete | Full message available | When response finishes |
tool_call | Tool is being invoked | When assistant calls a tool |
tool_result | Tool execution completed | After tool callback returns |
error | Error occurred | On any error |
done | Stream complete | End of stream |
Event Schemas
token Event:
{
"type": "token",
"token": "Hello",
"index": 0,
"message_id": "msg_123"
}
| Field | Type | Description |
|---|
type | string | Always "token" |
token | string | The text token (1-4 characters typically) |
index | integer | Token position in the response |
message_id | string | ID of the message being generated |
message_complete Event:
{
"type": "message_complete",
"message": {
"id": "msg_123",
"role": "assistant",
"content": "Hello! How can I help you today?",
"tool_calls": [],
"created_at": "2026-01-20T12:00:05Z"
}
}
tool_call Event:
{
"type": "tool_call",
"tool_call": {
"id": "tc_456",
"tool_name": "search_products",
"parameters": {
"query": "laptop"
},
"status": "pending"
},
"message_id": "msg_123"
}
tool_result Event:
{
"type": "tool_result",
"tool_call": {
"id": "tc_456",
"tool_name": "search_products",
"status": "completed",
"result": {
"products": [...]
}
},
"message_id": "msg_123"
}
error Event:
{
"type": "error",
"error": {
"code": "tool_callback_failed",
"message": "Tool callback timed out",
"tool_call_id": "tc_456"
},
"message_id": "msg_123"
}
done Event:
{
"type": "done",
"message_id": "msg_123"
}
SSE Wire Format
Events follow the standard SSE format:
event: token
data: {"type":"token","token":"Hello","index":0,"message_id":"msg_123"}
event: token
data: {"type":"token","token":" world","index":1,"message_id":"msg_123"}
event: message_complete
data: {"type":"message_complete","message":{...}}
event: done
data: {"type":"done","message_id":"msg_123"}
Important: Each event has an event: line followed by a data: line, then a blank line.
Authentication for Streaming
The standard browser EventSource API does not support custom headers. Use one of these approaches:
Option 1: Short-Lived Token in Query Parameter (Recommended for Browsers)
const { streaming_token } = await fetch('/api/v1/agents/rooms/{room_id}/stream-token', {
method: 'POST',
headers: { 'Authorization': `Bearer ${accessToken}` }
}).then(r => r.json());
const eventSource = new EventSource(
`https://core.interactor.com/api/v1/agents/rooms/${roomId}/stream?token=${streaming_token}`
);
Option 2: Fetch API with ReadableStream
async function streamWithAuth(roomId: string, accessToken: string) {
const response = await fetch(
`https://core.interactor.com/api/v1/agents/rooms/${roomId}/stream`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const reader = response.body?.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (reader) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split('\n\n');
buffer = events.pop() || '';
for (const eventBlock of events) {
if (!eventBlock.trim()) continue;
const lines = eventBlock.split();
eventType = ;
data = ;
( line lines) {
(line.()) {
eventType = line.();
} (line.()) {
data = line.();
}
}
(data) {
parsed = .(data);
(eventType, parsed);
}
}
}
}
() {
(data.) {
:
process..(data.);
;
:
.(, data..);
;
:
.(, data..);
;
:
.(, data..);
;
:
.();
;
}
}
Option 3: Node.js with eventsource Package
import EventSource from 'eventsource';
const eventSource = new EventSource(
`https://core.interactor.com/api/v1/agents/rooms/${roomId}/stream`,
{ headers: { 'Authorization': `Bearer ${accessToken}` } }
);
eventSource.addEventListener('token', (e) => {
const data = JSON.parse(e.data);
process.stdout.write(data.token);
});
eventSource.addEventListener('error', (e) => {
console.error('Stream error:', e);
eventSource.close();
});
Reconnection Handling
If the stream disconnects, reconnect with a Last-Event-ID header (if supported):
let lastEventId: string | null = null;
eventSource.onmessage = (e) => {
lastEventId = e.lastEventId;
};
eventSource.onerror = () => {
eventSource.close();
setTimeout(() => {
const newSource = new EventSource(
`${streamUrl}?last_event_id=${lastEventId}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
}, 1000);
};
Data Sources
Connect databases and APIs that assistants can query directly using natural language.
Register a Data Source
curl -X POST https://core.interactor.com/api/v1/data-sources \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "sales_database",
"type": "postgresql",
"connection": {
"host": "db.yourcompany.com",
"port": 5432,
"database": "sales",
"username": "readonly_user",
"password": "secure_password"
},
"description": "Sales and customer data including orders, products, and customer information"
}'
Response:
{
"data": {
"id": "ds_abc",
"name": "sales_database",
"type": "postgresql",
"status": "connected",
"schema_status": "extracting",
"created_at": "2026-01-20T12:00:00Z"
}
}
Interactor automatically extracts the database schema for the assistant to understand.
Supported Data Source Types
| Type | Description |
|---|
postgresql | PostgreSQL database |
mysql | MySQL database |
mssql | Microsoft SQL Server |
mongodb | MongoDB database |
rest_api | REST API endpoint |
List Data Sources
curl https://core.interactor.com/api/v1/data-sources \
-H "Authorization: Bearer <token>"
Get Data Source
curl https://core.interactor.com/api/v1/data-sources/ds_abc \
-H "Authorization: Bearer <token>"
Response includes schema information:
{
"data": {
"id": "ds_abc",
"name": "sales_database",
"type": "postgresql",
"status": "connected",
"schema_status": "ready",
"tables": [
{
"name": "customers",
"columns": [
{"name": "id", "type": "uuid", "nullable": false},
{"name": "email", "type": "varchar",
...
Update Data Source
curl -X PUT https://core.interactor.com/api/v1/data-sources/ds_abc \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated description with more context"
}'
Delete Data Source
curl -X DELETE https://core.interactor.com/api/v1/data-sources/ds_abc \
-H "Authorization: Bearer <token>"
Refresh Schema
Re-extract the schema if your database structure changed:
curl -X POST https://core.interactor.com/api/v1/data-sources/ds_abc/refresh-schema \
-H "Authorization: Bearer <token>"
Execute Query
Run a query directly against the data source:
curl -X POST https://core.interactor.com/api/v1/data-sources/ds_abc/query \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"query": "SELECT * FROM customers WHERE created_at > $1 LIMIT 10",
"parameters": ["2026-01-01"]
}'
Response:
{
"data": {
"columns": ["id", "email", "name", "created_at"],
"rows": [
["uuid-1", "john@example.com", "John Doe", "2026-01-15T10:00:00Z"],
["uuid-2", "jane@example.com", "Jane Smith", "2026-01-16T11:00:00Z"]
],
"row_count": 2,
"execution_time_ms": 45
}
}
Semantic Mappings
Add synonyms and descriptions to help the assistant understand your schema better:
curl -X PATCH https://core.interactor.com/api/v1/data-sources/ds_abc/semantic-mappings \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"customers": {
"description": "Customer accounts and profiles",
"synonyms": ["users", "clients", "accounts", "members"]
},
"customers.created_at": {
"description": "When the customer signed up",
"synonyms": ["signup date", "registration date", "joined date"]
},
"orders": {
"description": "Customer purchase orders",
"synonyms": ["purchases", "transactions", "sales"]
},
"orders.total_amount": {
"description": "Total order value in USD",
"synonyms": ["order total", "purchase amount", "sale value"]
}
}
}'
This helps the assistant translate natural language questions like:
- "How many users signed up last month?" →
SELECT COUNT(*) FROM customers WHERE created_at >= '2026-01-01'
- "What's the total sales this week?" →
SELECT SUM(total_amount) FROM orders WHERE created_at >= '2026-01-14'
Knowledge Base Search
Search for external services that assistants can connect to:
Search Services
curl -X POST https://core.interactor.com/api/v1/knowledge-base/services/search \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"query": "calendar scheduling",
"limit": 5
}'
Response:
{
"data": {
"services": [
{
"id": "google_calendar",
"name": "Google Calendar",
"description": "Calendar and scheduling service",
"auth_type": "oauth2",
"capabilities": ["create_event", "list_events", "update_event", "delete_event"]
},
{
"id": "microsoft_calendar",
"name": "Microsoft Outlook Calendar",
"description": "Microsoft 365 calendar service",
"auth_type": "oauth2",
"capabilities"
Lookup Service
curl -X POST https://core.interactor.com/api/v1/knowledge-base/services/lookup \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"service_id": "google_calendar"}'
Get Service Details
curl https://core.interactor.com/api/v1/knowledge-base/services/google_calendar \
-H "Authorization: Bearer <token>"
Get Service OAuth Config
curl https://core.interactor.com/api/v1/knowledge-base/services/google_calendar/oauth \
-H "Authorization: Bearer <token>"
Complete Implementation Example
TypeScript Implementation
import { InteractorClient } from './interactor-client';
export class AgentManager {
private client: InteractorClient;
constructor(client: InteractorClient) {
this.client = client;
}
async createAssistant(config: {
name: string;
title: string;
description?: string;
instructions: string;
modelConfig?: {
provider?: 'openai';
model?: string;
temperature?: number;
};
enabledTools?: string[];
}): Promise<Assistant> {
return this.client.request('POST', '/agents/assistants', {
name: config.name,
title: config.title,
description: config.description,
: config.,
: config.,
: config.
});
}
(): <[]> {
result = ..<{ : [] }>(
,
);
result.;
}
(: ): <> {
..(, );
}
(: , : <>): <> {
..(, , updates);
}
(: ): <> {
..(, );
}
(
: ,
: ,
?: <, >
): <> {
..(, , {
: ,
metadata
});
}
(?: {
?: ;
?: ;
?: | ;
}): <[]> {
params = ();
(filters?.) params.(, );
(filters?.) params.(, filters.);
(filters?.) params.(, filters.);
query = params.();
result = ..<{ : [] }>(
,
);
result.;
}
(: ): <> {
..(, );
}
(: ): <> {
..(, );
}
(: , : ): <> {
..(, , {
content,
:
});
}
(: , ?: {
?: ;
?: ;
}): <{ : []; : }> {
params = ();
(options?.) params.(, options..());
(options?.) params.(, options.);
query = params.();
..(
,
);
}
(: {
: ;
: ;
: <, >;
: ;
: ;
}): <> {
..(, , {
: tool.,
: tool.,
: tool.,
: tool.,
: tool.
});
}
(): <[]> {
result = ..<{ : [] }>(, );
result.;
}
(: ): <> {
..(, );
}
(: {
: ;
: | | | | ;
: <, >;
?: ;
}): <> {
..(, , config);
}
(): <[]> {
result = ..<{ : [] }>(
,
);
result.;
}
(: ): <> {
..(, );
}
(: ): <> {
..(, );
}
(
: ,
: ,
?: []
): <> {
..(, , {
query,
parameters
});
}
(
: ,
: <, { ?: ; ?: [] }>
): <> {
..(, , {
mappings
});
}
}
{
: ;
: ;
: ;
?: ;
: {
: ;
: ;
: ;
};
: [];
: ;
}
{
?: ;
?: ;
?: ;
?: {
?: ;
?: ;
?: ;
};
?: [];
}
{
: ;
: ;
: ;
: | ;
?: <, >;
: ;
: ;
?: ;
}
{
: ;
: | | | ;
: ;
?: [];
: ;
}
{
: ;
: ;
: <, >;
?: ;
}
{
: ;
: ;
: ;
: <, >;
: ;
: ;
}
{
: ;
: ;
: ;
: | | ;
: | | ;
?: [];
: ;
}
{
: ;
: [];
}
{
: ;
: ;
: ;
}
{
: [];
: [][];
: ;
: ;
}
Chat Interface Example
async function chat(userId: string, assistantId: string) {
const agentManager = new AgentManager(interactorClient);
let rooms = await agentManager.listRooms({
userId,
assistantId,
status: 'active'
});
let room: Room;
if (rooms.length > 0) {
room = rooms[0];
} else {
room = await agentManager.createRoom(assistantId, userId, {
user_name: 'John',
context: 'general_support'
});
}
await agentManager.sendMessage(room.id, 'How do I reset my password?');
await new Promise(resolve => setTimeout(resolve, 3000));
const { messages } = await agentManager.getMessages(room.);
( msg messages) {
.();
}
}
Webhook Events
Subscribe to agent events for real-time updates:
| Event | Description |
|---|
agent.room.message | New message in a room |
agent.room.closed | Room was closed |
Note: Additional events like agent.tool.invoked may be available depending on
your Interactor version. Check the interactor-webhooks skill or your API documentation
for the complete list of available events.
See interactor-webhooks skill for webhook setup and SSE streaming.
Best Practices
DO
- Keep instructions focused - Clear, specific instructions produce better results
- Use semantic mappings - Help assistants understand your data schema
- Secure tool callbacks - Always verify signatures on tool callbacks
- Use read-only database users - Limit data source connections to read-only access
- Monitor tool usage - Track which tools are being called and their success rates
- Test conversations - Verify assistant behavior before deploying to users
- Use metadata - Pass user context (name, plan, etc.) to personalize responses
DON'T
- Don't expose sensitive data - Be careful what tools can access
- Don't use write access - Keep data sources read-only
- Don't skip signature verification - Always verify tool callback signatures
- Don't overload with tools - Only enable tools the assistant actually needs
- Don't use vague instructions - Specific instructions produce better results
Error Handling
Agent-Specific Errors
Common error patterns you may encounter:
| Error Code | HTTP Status | Description | Resolution |
|---|
assistant_not_found | 404 | Assistant doesn't exist | Check assistant ID |
room_not_found | 404 | Room doesn't exist | Check room ID |
room_closed | 400 | Room is already closed | Create a new room |
tool_not_found | 404 | Tool doesn't exist | Check tool ID |
tool_callback_failed | 500 | Tool callback returned error | Check your callback endpoint |
data_source_disconnected | 400 | Database connection failed | Check connection settings |
query_execution_failed | 400 | SQL query failed | Check query syntax |
Note: Error codes and formats may vary. Always check the response body for
detailed error messages. Standard HTTP status codes apply (4xx for client errors,
5xx for server errors).
Rate Limiting
The AI Agents API uses rate limiting to ensure fair usage and system stability.
Rate Limit Headers
Every API response includes these headers:
| Header | Type | Description |
|---|
X-RateLimit-Limit | integer | Max requests allowed in window |
X-RateLimit-Remaining | integer | Requests remaining in current window |
X-RateLimit-Reset | integer | Unix timestamp (UTC) when window resets |
X-RateLimit-Resource | string | Which limit bucket this applies to |
Example Response Headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705752000
X-RateLimit-Resource: agents:messages
Default Rate Limits
Limits vary by plan. Typical defaults:
| Resource | Free | Pro | Enterprise |
|---|
| Assistants (create) | 10/min | 60/min | 300/min |
| Rooms (create) | 100/min | 600/min | 3000/min |
| Messages (send) | 60/min/room | 300/min/room | 1000/min/room |
| Tool registrations | 20/min | 100/min | 500/min |
| Data source queries | 100/min | 500/min | 2000/min |
| API calls (global) | 1000/min | 10000/min | 100000/min |
Check Your Limits: View your actual limits in the Interactor dashboard under
Settings → API → Rate Limits, or call GET /api/v1/account/limits.
Rate Limit Response (HTTP 429)
When rate limited:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705752000
Retry-After: 30
Content-Type: application/json
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded for agents:messages",
"resource": "agents:messages",
"retry_after": 30
}
}
Handling Rate Limits
Implementation with proper header parsing:
interface RateLimitInfo {
limit: number;
remaining: number;
resetAt: Date;
resource: string;
}
function parseRateLimitHeaders(headers: Headers): RateLimitInfo {
return {
limit: parseInt(headers.get('X-RateLimit-Limit') || '0'),
remaining: parseInt(headers.get('X-RateLimit-Remaining') || '0'),
resetAt: new Date(parseInt(headers.get('X-RateLimit-Reset') || '0') * 1000),
resource: headers.get('X-RateLimit-Resource') || 'unknown'
};
}
async function withRateLimitHandling<T>(
fn: () => Promise<Response>,
maxRetries: number = 3
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
response = ();
rateLimit = (response.);
.();
(response.) {
response.();
}
(response. === ) {
retryAfter = (response..() || );
.();
( (resolve, retryAfter * ));
;
}
();
}
();
}
(): <> {
response = (, {
: ,
: { : , : },
: .({ content, : })
});
rateLimit = (response.);
(rateLimit. < rateLimit. * ) {
waitTime = .((rateLimit..() - .()) / );
.();
}
response.();
}
Troubleshooting
Common Issues
Tool Callback Not Receiving Requests
Symptoms: Assistant says it's calling a tool, but your callback endpoint never receives the request.
Checklist:
- ✓ Verify
callback_url is publicly accessible (not localhost)
- ✓ Check firewall/security group allows inbound HTTPS
- ✓ Ensure endpoint returns 200 status within 30 seconds
- ✓ Verify SSL certificate is valid (no self-signed in production)
Debug: Check Interactor dashboard for tool execution logs.
Tool Callback Signature Verification Failing
Symptoms: All tool callbacks return 401 Unauthorized.
Checklist:
- ✓ Verify
callback_secret matches what you registered
- ✓ Ensure you're reading raw request body (not parsed JSON)
- ✓ Check signature header name:
X-Interactor-Signature
- ✓ Verify HMAC algorithm is SHA256
Debug:
console.log('Received signature:', signature);
console.log('Payload:', payload);
console.log('Expected:', `sha256=${crypto.createHmac('sha256', secret).update(payload).digest('hex')}`);
Data Source Connection Failed
Symptoms: Data source status shows "disconnected" or "error".
Checklist:
- ✓ Verify database host is reachable from Interactor's servers
- ✓ Check credentials are correct
- ✓ Ensure database user has SELECT permissions
- ✓ Verify SSL/TLS settings match your database configuration
- ✓ Check if IP allowlisting is required
Common Fixes:
GRANT SELECT ON ALL TABLES IN SCHEMA public TO interactor_readonly;
GRANT USAGE ON SCHEMA public TO interactor_readonly;
Assistant Not Using Tools
Symptoms: Assistant responds without using available tools when it should.
Checklist:
- ✓ Verify tool is in
enabled_tools array for the assistant
- ✓ Check tool description is clear about when to use it
- ✓ Ensure instructions mention when to use tools
- ✓ Verify tool parameters schema is valid JSON Schema
Fix: Update assistant instructions to be explicit:
"When users ask about products, ALWAYS use the search_products tool to find current information."
Messages Not Appearing in Room
Symptoms: Sent messages don't show up when listing messages.
Checklist:
- ✓ Verify room status is "active" (not "closed")
- ✓ Check message was acknowledged (201 response)
- ✓ Ensure you're querying the correct room ID
- ✓ Wait for async processing (use streaming for real-time)
High Latency on Responses
Symptoms: Assistant takes a long time to respond.
Possible Causes:
- Tool callbacks taking too long → Optimize your callback endpoints
- Large context window → Close old rooms, start fresh conversations
- Complex instructions → Simplify system prompt
- Data source queries slow → Add indexes, optimize queries
Monitor:
const start = Date.now();
await sendMessage(roomId, content);
console.log(`Message sent in ${Date.now() - start}ms`);
Security & Compliance
Data Retention
| Data Type | Default Retention | Configurable |
|---|
| Messages | 90 days | Yes (30-365 days) |
| Room metadata | Until room deleted | N/A |
| Tool execution logs | 30 days | Yes (7-90 days) |
| Assistant configurations | Indefinite | N/A |
Enterprise: Contact support for custom retention policies and data residency requirements.
Configure retention in your Interactor dashboard under Settings → Data Management → Retention Policies.
Data Deletion (GDPR/CCPA)
To delete user data for compliance with GDPR "Right to Erasure" or CCPA:
Delete all rooms for a user:
curl "https://core.interactor.com/api/v1/agents/rooms?namespace=user_123" \
-H "Authorization: Bearer <token>"
curl -X DELETE "https://core.interactor.com/api/v1/agents/rooms/room_xyz" \
-H "Authorization: Bearer <token>"
Bulk deletion via API:
curl -X POST https://core.interactor.com/api/v1/data/delete-user-data \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "user_123",
"include_messages": true,
"include_tool_logs": true,
"confirmation": "DELETE_ALL_DATA_FOR_user_123"
}'
Response:
{
"data": {
"deletion_id": "del_abc123",
"status": "processing",
"estimated_completion": "2026-01-20T13:00:00Z",
"items_to_delete": {
"rooms": 5,
"messages": 142,
"tool_logs": 23
}
}
}
Important: Data deletion is asynchronous. Poll the deletion status endpoint to confirm completion
before responding to user deletion requests.
Secret Rotation
Rotate callback_secret for tools without downtime:
Step 1: Add new secret (dual-secret mode):
curl -X PATCH https://core.interactor.com/api/v1/tools/tool_abc/secrets \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"add_secret": "new_secret_value_here",
"deprecate_old_after_hours": 24
}'
Step 2: Update your callback handler to accept both secrets:
function verifyWithRotation(
payload: string,
signature: string,
secrets: string[]
): boolean {
const signatureValue = signature.replace('sha256=', '');
for (const secret of secrets) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (crypto.timingSafeEqual(
Buffer.from(signatureValue),
Buffer.from(expected)
)) {
return true;
}
}
return false;
}
const SECRETS = [
process.env.TOOL_CALLBACK_SECRET_NEW!,
process.env.TOOL_CALLBACK_SECRET_OLD!
];
const isValid = verifyWithRotation(payload, signature, SECRETS);
Step 3: After transition period, remove old secret:
curl -X PATCH https://core.interactor.com/api/v1/tools/tool_abc/secrets \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"remove_deprecated": true
}'
PII Handling
Do NOT include PII in:
- Tool names or descriptions
- Assistant names
- Log messages
DO use:
- Namespaces for user identification (e.g.,
user_123)
- Encrypted metadata for sensitive context
- Room metadata (encrypted at rest) for PII when necessary
{ "user_email": "john@example.com" }
{ "user_id": "user_123" }
Observability & Correlation IDs
Request Correlation
Every API request returns a correlation ID for tracing:
Response Headers:
X-Request-Id: req_abc123xyz
X-Trace-Id: trace_789def
Include these in support requests and logging:
async function apiRequest(path: string, options: RequestInit = {}) {
const response = await fetch(`https://core.interactor.com/api/v1${path}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'X-Client-Request-Id': crypto.randomUUID()
}
});
const requestId = response.headers.get('X-Request-Id');
const traceId = response.headers.get('X-Trace-Id');
console.log(`[${path}] Request: ${requestId}, Trace: ${traceId}`);
return { response, requestId, traceId };
}
Logging Recommendations
import { Logger } from 'your-logging-library';
const logger = new Logger({
service: 'my-app',
version: process.env.APP_VERSION
});
async function sendMessageWithLogging(roomId: string, content: string) {
const correlationId = crypto.randomUUID();
logger.info('Sending message to AI agent', {
correlationId,
roomId,
contentLength: content.length
});
try {
const { response, requestId } = await apiRequest(
`/agents/rooms/${roomId}/messages`,
{
method: 'POST',
body: JSON.stringify({ content, role: 'user' })
}
);
if (!response.ok) {
logger.error('Message send failed', {
correlationId,
requestId,
status: response.status
});
throw new Error();
}
data = response.();
logger.(, {
correlationId,
requestId,
: data..
});
data;
} (error) {
logger.(, {
correlationId,
: error.
});
error;
}
}
Metrics to Track
| Metric | Description | Alert Threshold |
|---|
agent.messages.sent | Messages sent to assistants | N/A (volume) |
agent.messages.latency_ms | Time to first token | > 5000ms |
agent.tool_calls.total | Tool invocations | N/A (volume) |
agent.tool_calls.errors | Failed tool callbacks | > 5% error rate |
agent.tool_calls.latency_ms | Tool callback duration | > 10000ms |
agent.rooms.active | Currently active rooms | Depends on scale |
agent.rate_limits.hits | 429 responses received | > 10/min |
Dashboard Integration
Export metrics to your monitoring system:
import { metrics } from 'your-metrics-library';
const sendStart = Date.now();
await sendMessage(roomId, content);
metrics.histogram('agent.messages.latency_ms', Date.now() - sendStart, {
assistant_id: assistantId
});
app.post('/api/tools/:toolName', async (req, res) => {
const start = Date.now();
try {
const result = await handleToolCallback(req);
metrics.increment('agent.tool_calls.total', { tool: req.params.toolName, status: 'success' });
res.json({ result });
} catch (error) {
metrics.increment('agent.tool_calls.errors', { tool: req.params.toolName });
throw error;
} finally {
metrics.histogram('agent.tool_calls.latency_ms', Date.now() - start, {
tool: req.params.toolName
});
}
});
Message Limits & Attachments
Message Size Limits
| Content Type | Max Size | Notes |
|---|
| Text message | 32 KB | UTF-8 encoded |
| Metadata object | 16 KB | JSON serialized |
| Tool result | 64 KB | JSON serialized |
| Total request | 128 KB | Including headers |
Messages exceeding these limits return 413 Payload Too Large.
Handling Large Content
For content that may exceed limits:
const MAX_MESSAGE_SIZE = 32 * 1024;
function truncateForAssistant(content: string): string {
if (content.length <= MAX_MESSAGE_SIZE) {
return content;
}
const truncated = content.slice(0, MAX_MESSAGE_SIZE - 100);
return truncated + '\n\n[Content truncated. Full content available via reference ID: ref_xxx]';
}
async function sendLargeDocument(roomId: string, document: string) {
if (document.length <= MAX_MESSAGE_SIZE) {
return sendMessage(roomId, document);
}
const docId = await storeDocument(document);
return sendMessage(roomId,
`I have a document to analyze (${document.length} characters). ` +
+
);
}
Attachment Support
API Note: File attachment support varies by Interactor version. Check your API documentation
or contact support for availability.
If supported, the typical pattern:
curl -X POST https://core.interactor.com/api/v1/attachments \
-H "Authorization: Bearer <token>" \
-F "file=@document.pdf" \
-F "purpose=message_attachment"
curl -X POST https://core.interactor.com/api/v1/agents/rooms/room_xyz/messages \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"content": "Please analyze this document",
"role": "user",
"attachments": ["att_xyz"]
}'
Supported file types (when available):
- Documents: PDF, DOCX, TXT, MD
- Images: PNG, JPG, GIF, WEBP
- Data: CSV, JSON, XML
Max file size: 10 MB per file, 25 MB total per message.
API Reference & Testing
OpenAPI Specification
The AI Agents API follows OpenAPI 3.0 specification. Access the spec at:
https://core.interactor.com/api/v1/openapi.json
https://core.interactor.com/api/v1/openapi.yaml
Generate Client Libraries:
npx openapi-typescript-codegen \
--input https://core.interactor.com/api/v1/openapi.json \
--output ./generated/interactor-client
pip install openapi-python-client
openapi-python-client generate \
--url https://core.interactor.com/api/v1/openapi.json
Testing Your Integration
1. Use the Sandbox Environment:
export INTERACTOR_URL="https://sandbox.interactor.com"
export INTERACTOR_SANDBOX_TOKEN="sandbox_token_here"
2. Test Tool Callbacks Locally:
Use a tunnel service to expose your local endpoint:
ngrok http 3000
curl -X POST https://sandbox.interactor.com/api/v1/tools \
-H "Authorization: Bearer $INTERACTOR_SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "test_tool",
"description": "Test tool for development",
"parameters": {"type": "object", "properties": {}},
"callback_url": "https://abc123.ngrok.io/api/tools/test"
}'
3. Verify Webhook Signatures in Tests:
import { describe, it, expect } from 'vitest';
import crypto from 'crypto';
describe('Tool Callback Verification', () => {
const SECRET = 'test_secret';
it('should verify valid signature', () => {
const payload = JSON.stringify({ tool_name: 'test', parameters: {} });
const signature = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(payload)
.digest('hex');
const result = verifyToolCallback(payload, signature, SECRET);
expect(result.valid).toBe(true);
});
it('should reject invalid signature', () => {
const payload = JSON.stringify({ tool_name: 'test', parameters: {} });
const signature = 'sha256=invalid';
const result = verifyToolCallback(payload, signature, );
(result.).();
});
(, {
payload = .({ : , : {} });
oldTimestamp = (.() - ).();
signature = + crypto
.(, )
.(payload)
.();
result = (payload, signature, oldTimestamp, , );
(result.).();
(result.).();
});
});
4. Integration Test Example:
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
describe('AI Agents Integration', () => {
let assistantId: string;
let roomId: string;
beforeAll(async () => {
const assistant = await createAssistant({
name: `test_${Date.now()}`,
title: 'Test Assistant',
instructions: 'You are a test assistant. Always respond with "Test OK".'
});
assistantId = assistant.id;
});
afterAll(async () => {
if (roomId) await closeRoom(roomId);
if (assistantId) await deleteAssistant(assistantId);
});
it('should create a room and exchange messages', async () => {
const room = await createRoom(assistantId, 'test_user');
roomId = room.id;
expect(room.status).toBe();
sent = (roomId, );
(sent.).();
( (r, ));
{ messages } = (roomId);
(messages.).();
assistantMessage = messages.( m. === );
(assistantMessage).();
(assistantMessage?.).();
});
});
Output Format
When implementing AI agents, provide this summary:
## AI Agent Implementation Report
**Date**: YYYY-MM-DD
**Assistant**: support_assistant
### Configuration
| Setting | Value |
|---------|-------|
| Model | gpt-4o |
| Temperature | 0.7 |
| Tools | search_products, create_ticket |
### Tools Registered
| Tool | Callback URL | Status |
|------|--------------|--------|
| search_products | https://app.com/api/tools/search | ✓ Active |
| create_ticket | https://app.com/api/tools/ticket | ✓ Active |
### Data Sources Connected
| Name | Type | Status |
|------|------|--------|
| sales_database | PostgreSQL | ✓ Connected |
### Implementation Checklist
- [ ] Assistant created with instructions
- [ ] Tools registered with callbacks
- [ ] Tool callback signature verification
- [ ] Data sources connected
- [ ] Semantic mappings configured
- [ ] Room management implemented
- [ ] Message streaming setup
- [ ] Error handling implemented
### Next Steps
1. Test conversations with sample queries
2. Set up webhooks for real-time updates (see `interactor-webhooks` skill)
3. Implement streaming for real-time responses
Related Skills
- interactor-auth: Setup authentication (prerequisite)
- interactor-sdk: TypeScript/JavaScript SDK for API integration
- interactor-credentials: Agents can use credentials to access external services
- interactor-workflows: Combine AI agents with automated workflows
- interactor-webhooks: Real-time message streaming and event subscriptions