SmoothAgent API

Base URL: https://agent.smoothcodes.com

Quick Start

Get from zero to a working AI agent in 4 steps.

Step 1: Create an account

curl -X POST https://agent.smoothcodes.com/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your-password-here"}'

Response:

{
  "token": "eyJhbGciOi...",
  "devId": "dev_a1b2c3d4e5f6",
  "email": "you@example.com",
  "verified": true
}

Save the token value. This is your DEV_TOKEN (valid for 7 days).

Step 2: Create an agent

curl -X POST https://agent.smoothcodes.com/dashboard/configs \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer DEV_TOKEN" \
  -d '{
    "llmProvider": "anthropic",
    "llmApiKey": "sk-ant-api03-your-key-here",
    "mcpUrl": "https://your-mcp-server.example.com/mcp",
    "systemPrompt": "You are a helpful assistant.",
    "themeName": "My Agent"
  }'

Response:

{ "id": "cfg_a1b2c3d4e5f67890" }

Save the id value. This is your CONFIG_ID.

Step 3: Generate a user token

curl -X POST https://agent.smoothcodes.com/dashboard/configs/CONFIG_ID/token \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer DEV_TOKEN" \
  -d '{"userId": "user_123", "expiresInDays": "30"}'

Response:

{
  "token": "eyJhbGciOi...",
  "configId": "cfg_a1b2c3d4e5f67890",
  "userId": "user_123",
  "expiresIn": "30 days"
}

This USER_TOKEN is what your end-users use to chat.

Step 4: Send a message

First, create a chat:

curl -X POST https://agent.smoothcodes.com/chats \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer USER_TOKEN" \
  -d '{"title": "My first chat"}'

Response:

{
  "id": "chat_a1b2c3d4e5f6",
  "configId": "cfg_a1b2c3d4e5f67890",
  "userId": "user_123",
  "title": "My first chat",
  "messages": []
}

Then, send a prompt (SSE stream):

curl -N -X POST https://agent.smoothcodes.com/chats/CHAT_ID/prompt \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer USER_TOKEN" \
  -d '{"content": "Hello, what can you do?"}'

Response headers include X-Prompt-Id, X-Chat-Id, and X-Request-Id.

Response body is an SSE stream:

data: {"type":"text-delta","delta":"Hello"}
data: {"type":"text-delta","delta":"! I"}
data: {"type":"text-delta","delta":" can help"}
data: {"type":"text-delta","delta":" you with..."}
data: {"type":"finish","finishReason":"stop"}
data: [DONE]

Authentication

SmoothAgent uses JWT Bearer tokens. Include the token in the Authorization header:

Authorization: Bearer YOUR_TOKEN

Token Types

TypeScopeExpiryUse case
Devdev7 daysDashboard access, agent management, usage viewing
Adminadmin, chat1 yearBackend automation, token generation, server-to-server
Userchat1-365 days (configurable)End-user chat access, scoped to one agent
Guestdev90 daysAuto-created anonymous accounts, convertible to real accounts

Typical Flow

  1. Developer signs up and gets a DEV_TOKEN
  2. Developer creates agents via the Dashboard API using the DEV_TOKEN
  3. Developer generates an ADMIN_TOKEN for their backend
  4. Backend uses ADMIN_TOKEN to generate USER_TOKENs for end-users
  5. End-users chat using their USER_TOKEN (scoped to one agent)
Security: Never expose your DEV_TOKEN or ADMIN_TOKEN in frontend code. Only USER_TOKENs should be sent to browsers.

SDK

Install the SDK:

npm install smoothagent

Constructor

import { SmoothAgent } from 'smoothagent';

const agent = new SmoothAgent({
  token: 'USER_TOKEN',        // User JWT for chat access
  adminToken: 'ADMIN_TOKEN',  // Optional: admin JWT for management
  workerUrl: 'https://agent.smoothcodes.com',       // Optional: defaults to window.location.origin
});

Auth Methods

// Sign up
const { token, devId, email } = await agent.signup('you@example.com', 'password123');

// Log in
const { token, devId, email, plan } = await agent.login('you@example.com', 'password123');

// Get current user info
const me = await agent.me();

Chat Methods

// Create a chat
const chat = await agent.createChat('My Chat Title');

// List all chats
const chats = await agent.listChats();

// Get a specific chat with messages
const chatWithMessages = await agent.getChat('chat_abc123');

// Delete a chat
await agent.deleteChat('chat_abc123');

Prompt (Callback Style)

const result = await agent.prompt(chat.id, 'Explain quantum computing', {
  onDelta: (delta, fullText) => {
    // Partial text token received
    process.stdout.write(delta);
  },
  onThinking: (delta, fullThinking) => {
    // Extended thinking/reasoning token
    console.log('[thinking]', delta);
  },
  onToolCall: ({ toolCallId, toolName }) => {
    // Tool was called
    console.log('Tool called:', toolName);
  },
  onToolResult: ({ toolCallId, result }) => {
    // Tool returned a result
    console.log('Tool result:', result);
  },
  onFinish: ({ text, reason }) => {
    // Stream completed
    console.log('Done:', reason);
  },
  onError: (errorMessage) => {
    // Error occurred
    console.error('Error:', errorMessage);
  },
  config: {
    // Optional: override agent config for this prompt
    llmModel: 'claude-haiku-4-5-20251001',
    temperature: '0.5',
  },
});

// result: { text, promptId, usage, finishReason, steps }

Prompt (Async Iterator)

for await (const event of agent.stream(chat.id, 'Tell me a story')) {
  switch (event.type) {
    case 'text-delta':
      process.stdout.write(event.delta);
      break;
    case 'thinking-delta':
      console.log('[thinking]', event.delta);
      break;
    case 'tool-call':
      console.log('Tool:', event.toolName);
      break;
    case 'tool-result':
      console.log('Result:', event.result);
      break;
    case 'finish':
      console.log('\nDone:', event.reason, 'Usage:', event.usage);
      break;
    case 'error':
      console.error('Error:', event.message);
      break;
  }
}

Stateless Chat (No History)

const result = await agent.chat(
  [{ role: 'user', content: 'What is 2+2?' }],
  {
    onDelta: (delta) => process.stdout.write(delta),
    config: {
      llmProvider: 'anthropic',
      llmApiKey: 'sk-ant-api03-...',
      llmModel: 'claude-haiku-4-5-20251001',
    },
  }
);

Agent Management

// Create agent
const { id } = await agent.createAgent({
  llmProvider: 'anthropic',
  llmApiKey: 'sk-ant-api03-...',
  mcpUrl: 'https://mcp.example.com/mcp',
  systemPrompt: 'You are helpful.',
  themeName: 'Support Bot',
});

// List agents
const agents = await agent.listAgents();

// Update agent
await agent.updateAgent('cfg_abc123', { systemPrompt: 'Updated prompt' });

// Delete agent
await agent.deleteAgent('cfg_abc123');

// Generate user token
const { token } = await agent.createUser('user_456', { configId: 'cfg_abc123' });

Usage and Health

// Get usage stats
const usage = await agent.getUsage('cfg_abc123', 7); // last 7 days

Cancel Running Prompt

// Cancel is automatic via AbortController in the SDK's fetch retry logic.
// From the API, you can also:
await fetch(`${BASE}/chats/${chatId}/prompt`, {
  method: 'DELETE',
  headers: { 'Authorization': 'Bearer USER_TOKEN' },
});

Auth API

POST /auth/signup

Create a new developer account. Optionally converts a guest account.

Request

curl -X POST https://agent.smoothcodes.com/auth/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "dev@example.com",
    "password": "securepass123",
    "guestId": "dev_guest123"
  }'
FieldTypeRequiredDescription
emailstringYesValid email address
passwordstringYesMinimum 8 characters
guestIdstringNoGuest dev ID to convert (preserves chats and configs)

Response 201 Created

{
  "token": "eyJhbGciOi...",
  "devId": "dev_a1b2c3d4e5f6",
  "email": "dev@example.com",
  "chatToken": "eyJhbGciOi...",
  "verified": true
}

Errors

StatusErrorCause
400Invalid emailEmail format is invalid
400Password must be at least 8 charactersPassword too short
400Invalid guest sessionguestId does not exist or is not a guest
409Email already registeredAccount with this email exists
POST /auth/login

Log in with email and password. Optionally transfers guest chats to the account.

Request

curl -X POST https://agent.smoothcodes.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "dev@example.com",
    "password": "securepass123",
    "guestId": "dev_guest123"
  }'
FieldTypeRequiredDescription
emailstringYesRegistered email address
passwordstringYesAccount password
guestIdstringNoGuest dev ID whose chats/configs will be transferred

Response 200 OK

{
  "token": "eyJhbGciOi...",
  "devId": "dev_a1b2c3d4e5f6",
  "email": "dev@example.com",
  "plan": "pro",
  "chatToken": "eyJhbGciOi..."
}

Errors

StatusErrorCause
400Invalid emailEmail format is invalid
400Password must be at least 8 charactersPassword too short
401Invalid email or passwordCredentials do not match
403Email not verified. Check your inbox.Account exists but email not verified
429Too many attempts. Try again in N minutes.Brute force lockout (5 attempts, 15min cooldown)
GET /auth/me

Get the currently authenticated developer's profile.

Request

curl https://agent.smoothcodes.com/auth/me \
  -H "Authorization: Bearer DEV_TOKEN"

Response 200 OK

{
  "id": "dev_a1b2c3d4e5f6",
  "email": "dev@example.com",
  "plan": "pro",
  "createdAt": "2025-01-15T10:30:00.000Z"
}

Errors

StatusErrorCause
401Authentication requiredMissing or invalid Bearer token
404Dev not foundAccount was deleted
POST /auth/guest

Create an anonymous guest account. No email or password needed. Guest accounts can later be converted to real accounts via signup.

Request

curl -X POST https://agent.smoothcodes.com/auth/guest

Response 201 Created

{
  "token": "eyJhbGciOi...",
  "devId": "dev_g1h2i3j4k5l6",
  "guest": true
}
POST /auth/forgot-password

Request a password reset email. Always returns success to avoid revealing whether an email is registered.

Request

curl -X POST https://agent.smoothcodes.com/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com"}'
FieldTypeRequiredDescription
emailstringYesAccount email address

Response 200 OK

{
  "ok": true,
  "message": "If that email exists, a reset link was sent."
}
POST /auth/reset-password

Reset password using a token received via email. Tokens expire after 1 hour.

Request

curl -X POST https://agent.smoothcodes.com/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
    "newPassword": "newsecurepass456"
  }'
FieldTypeRequiredDescription
tokenstringYesReset token from the email link
newPasswordstringYesNew password (minimum 8 characters)

Response 200 OK

{
  "ok": true,
  "message": "Password updated"
}

Errors

StatusErrorCause
400Invalid or missing tokenToken is empty or malformed
400Password must be at least 8 charactersNew password too short
400Invalid or expired reset tokenToken has expired (1h) or was already used

Dashboard API

All Dashboard endpoints require a DEV_TOKEN in the Authorization header.

GET /dashboard/configs

List all agents (configs) belonging to the authenticated developer.

Request

curl https://agent.smoothcodes.com/dashboard/configs \
  -H "Authorization: Bearer DEV_TOKEN"

Response 200 OK

{
  "configs": [
    {
      "id": "cfg_a1b2c3d4e5f67890",
      "mcpUrl": "https://mcp.example.com/mcp",
      "mcpServers": [{"name": "agent", "url": "https://mcp.example.com/mcp"}],
      "llmProvider": "anthropic",
      "llmModel": "claude-sonnet-4-20250514",
      "llmBaseUrl": null,
      "themeName": "Support Bot",
      "themeColor": "#6366f1",
      "systemPrompt": "You are a helpful assistant.",
      "allowedOrigins": null,
      "hasMcpToken": 0,
      "plan": "pro",
      "maxHistory": null,
      "maxTokens": null,
      "temperature": null,
      "maxSteps": 15,
      "maxBudgetUsd": null,
      "webSearch": 0,
      "webhookUrl": null,
      "createdAt": "2025-01-15T10:30:00.000Z",
      "updatedAt": "2025-01-15T12:00:00.000Z"
    }
  ]
}

Errors

StatusErrorCause
401Login requiredMissing or invalid DEV_TOKEN
POST /dashboard/configs

Create a new agent. All fields are optional; configure what you need.

Request

curl -X POST https://agent.smoothcodes.com/dashboard/configs \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer DEV_TOKEN" \
  -d '{
    "llmProvider": "anthropic",
    "llmApiKey": "sk-ant-api03-your-key",
    "llmModel": "claude-sonnet-4-20250514",
    "mcpUrl": "https://mcp.example.com/mcp",
    "mcpToken": "Bearer mcp-secret",
    "systemPrompt": "You are a helpful support agent.",
    "themeName": "Support Bot",
    "themeColor": "#6366f1",
    "maxSteps": 10,
    "maxHistory": 50,
    "temperature": 0.7,
    "webSearch": true
  }'

See Agent Config Fields for all available fields.

Response 201 Created

{ "id": "cfg_a1b2c3d4e5f67890" }

Errors

StatusErrorCause
401Login requiredMissing or invalid DEV_TOKEN
PUT /dashboard/configs/:id

Update an existing agent. Only send the fields you want to change.

Request

curl -X PUT https://agent.smoothcodes.com/dashboard/configs/cfg_a1b2c3d4e5f67890 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer DEV_TOKEN" \
  -d '{
    "systemPrompt": "Updated system prompt.",
    "maxTokens": 4096,
    "temperature": 0.3
  }'

Response 200 OK

{ "ok": true }

Errors

StatusErrorCause
400Nothing to updateRequest body has no recognized fields
401Login requiredMissing or invalid DEV_TOKEN
404Config not foundConfig does not exist or is not owned by this dev
DELETE /dashboard/configs/:id

Delete an agent permanently.

Request

curl -X DELETE https://agent.smoothcodes.com/dashboard/configs/cfg_a1b2c3d4e5f67890 \
  -H "Authorization: Bearer DEV_TOKEN"

Response 200 OK

{ "ok": true }

Errors

StatusErrorCause
401Login requiredMissing or invalid DEV_TOKEN
404Config not foundConfig does not exist or is not owned by this dev
POST /dashboard/configs/:id/token

Generate a user token for end-user chat access, scoped to one agent.

Request

curl -X POST https://agent.smoothcodes.com/dashboard/configs/cfg_a1b2c3d4e5f67890/token \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer DEV_TOKEN" \
  -d '{
    "userId": "user_abc123",
    "expiresInDays": "30"
  }'
FieldTypeRequiredDescription
userIdstringNoCustom user ID. Auto-generated if omitted (user_xxx).
expiresInDaysstringNoToken lifetime in days (1-365, default: 365)

Response 201 Created

{
  "token": "eyJhbGciOi...",
  "configId": "cfg_a1b2c3d4e5f67890",
  "userId": "user_abc123",
  "expiresIn": "30 days"
}

Errors

StatusErrorCause
401Login requiredMissing or invalid DEV_TOKEN
404Config not foundConfig does not exist or is not owned by this dev
GET /dashboard/usage

Get usage statistics for an agent or all agents.

Request

curl "https://agent.smoothcodes.com/dashboard/usage?configId=cfg_a1b2c3d4e5f67890&days=7" \
  -H "Authorization: Bearer DEV_TOKEN"
ParamTypeRequiredDescription
configIdstringNoFilter by agent. If omitted, returns all agents' usage.
daysnumberNoNumber of days to look back (default: 7)

Response 200 OK

{
  "totalPrompts": 142,
  "totalTokensIn": 50000,
  "totalTokensOut": 120000,
  "totalToolCalls": 35,
  "daily": [
    { "date": "2025-01-15", "prompts": 20, "tokensIn": 7000, "tokensOut": 17000 }
  ]
}

Errors

StatusErrorCause
401Login requiredMissing or invalid DEV_TOKEN
404Config not foundconfigId is not owned by this dev
GET /dashboard/chats

List all end-user chats across the developer's agents.

Request

curl "https://agent.smoothcodes.com/dashboard/chats?limit=20" \
  -H "Authorization: Bearer DEV_TOKEN"
ParamTypeRequiredDescription
limitnumberNoMaximum chats to return (default: 50)

Response 200 OK

{
  "chats": [
    {
      "id": "chat_a1b2c3d4e5f6",
      "configId": "cfg_a1b2c3d4e5f67890",
      "userId": "user_abc123",
      "title": "Help with billing",
      "configName": "Support Bot",
      "createdAt": "2025-01-15T10:30:00.000Z",
      "updatedAt": "2025-01-15T12:00:00.000Z"
    }
  ]
}

Errors

StatusErrorCause
401Login requiredMissing or invalid DEV_TOKEN
GET /dashboard/mcp-health

Check the health of an agent's MCP server.

Request

curl "https://agent.smoothcodes.com/dashboard/mcp-health?configId=cfg_a1b2c3d4e5f67890" \
  -H "Authorization: Bearer DEV_TOKEN"
ParamTypeRequiredDescription
configIdstringYesAgent ID to check

Response 200 OK (online)

{
  "status": "online",
  "latency": 145,
  "serverInfo": { "name": "my-mcp-server", "version": "1.0" },
  "tools": "available"
}

Response 200 OK (offline)

{
  "status": "offline",
  "latency": 3000,
  "error": "unreachable"
}

Errors

StatusErrorCause
400configId requiredMissing configId query parameter
401Login requiredMissing or invalid DEV_TOKEN
404Config not foundConfig does not exist or is not owned by this dev
PUT /dashboard/configs/:id/plan

Change the plan for an agent.

Request

curl -X PUT https://agent.smoothcodes.com/dashboard/configs/cfg_a1b2c3d4e5f67890/plan \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer DEV_TOKEN" \
  -d '{"plan": "pro"}'
FieldTypeRequiredDescription
planstringYesOne of: free, pro, enterprise

Response 200 OK

{ "ok": true, "plan": "pro" }

Errors

StatusErrorCause
400Invalid planPlan is not free, pro, or enterprise
401Login requiredMissing or invalid DEV_TOKEN
404Not foundConfig does not exist or is not owned by this dev

Billing API

Billing is handled through Stripe. All endpoints require a DEV_TOKEN.

Plans

PlanPriceIncluded ConcurrentDescription
Free$0/mo1For testing and personal projects
Pro$5/mo10For production apps, unlimited agents
Business$19/mo100For growing teams and apps
Enterprise$99/mo1,000+Isolated D1 database, dedicated VPS option
POST /billing/checkout

Create a Stripe Checkout session for subscribing to a plan.

Request

curl -X POST https://agent.smoothcodes.com/billing/checkout \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer DEV_TOKEN" \
  -d '{"plan": "pro"}'
FieldTypeRequiredDescription
planstringNoPlan to subscribe to: pro or enterprise (default: pro)

Response 200 OK

{
  "url": "https://checkout.stripe.com/c/pay/cs_live_...",
  "sessionId": "cs_live_..."
}

Redirect the user to the url to complete payment.

Errors

StatusErrorCause
400Invalid plan. Use: pro or enterpriseUnrecognized plan name
401Login requiredMissing or invalid DEV_TOKEN
404Dev not foundAccount was deleted
GET /billing/portal

Get a URL to the Stripe Customer Portal where the user can manage their subscription.

Request

curl https://agent.smoothcodes.com/billing/portal \
  -H "Authorization: Bearer DEV_TOKEN"

Response 200 OK

{
  "url": "https://billing.stripe.com/p/session/..."
}

Redirect the user to the url to manage billing.

Errors

StatusErrorCause
400No billing account. Subscribe first.No Stripe customer on file
401Login requiredMissing or invalid DEV_TOKEN

Chats API

Chats endpoints require a USER_TOKEN (or DEV_TOKEN for dev-owned chats).

POST /chats

Create a new chat session.

Request

curl -X POST https://agent.smoothcodes.com/chats \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer USER_TOKEN" \
  -d '{"title": "Help with billing"}'
FieldTypeRequiredDescription
titlestringNoChat title. Auto-set from first message if omitted.

Response 201 Created

{
  "id": "chat_a1b2c3d4e5f6",
  "configId": "cfg_a1b2c3d4e5f67890",
  "userId": "user_abc123",
  "title": "Help with billing",
  "messages": []
}

Errors

StatusErrorCause
401Authentication requiredMissing or invalid token
GET /chats

List the authenticated user's chats, ordered by last updated.

Request

curl https://agent.smoothcodes.com/chats \
  -H "Authorization: Bearer USER_TOKEN"

Response 200 OK

{
  "chats": [
    {
      "id": "chat_a1b2c3d4e5f6",
      "configId": "cfg_a1b2c3d4e5f67890",
      "userId": "user_abc123",
      "title": "Help with billing",
      "createdAt": "2025-01-15T10:30:00.000Z",
      "updatedAt": "2025-01-15T12:00:00.000Z"
    }
  ]
}
GET /chats/:id

Get a chat with its full message history.

Request

curl https://agent.smoothcodes.com/chats/chat_a1b2c3d4e5f6 \
  -H "Authorization: Bearer USER_TOKEN"

Response 200 OK

{
  "id": "chat_a1b2c3d4e5f6",
  "configId": "cfg_a1b2c3d4e5f67890",
  "userId": "user_abc123",
  "title": "Help with billing",
  "messages": [
    { "role": "user", "content": "How do I upgrade my plan?" },
    { "role": "assistant", "content": "You can upgrade...", "model": "claude-sonnet-4-20250514" }
  ],
  "createdAt": "2025-01-15T10:30:00.000Z",
  "updatedAt": "2025-01-15T12:00:00.000Z"
}

Errors

StatusErrorCause
401Authentication requiredMissing or invalid token
404Chat not foundChat does not exist or is not accessible by this user
POST /chats/:id/prompt

Send a message to the agent and receive a streaming SSE response. The response includes X-Prompt-Id, X-Chat-Id, and X-Request-Id headers.

Request

curl -N -X POST https://agent.smoothcodes.com/chats/chat_a1b2c3d4e5f6/prompt \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer USER_TOKEN" \
  -d '{
    "content": "How do I upgrade my plan?",
    "files": [
      {
        "type": "image/png",
        "data": "iVBORw0KGgo...",
        "name": "screenshot.png"
      }
    ],
    "config": {
      "llmModel": "claude-haiku-4-5-20251001",
      "temperature": "0.5"
    }
  }'
FieldTypeRequiredDescription
contentstringYesThe user's message text
filesarrayNoFile attachments: [{type, data (base64), name}]
configobjectNoPer-prompt config overrides (model, temperature, etc.)

Response: SSE Stream

Content-Type: text/event-stream

data: {"type":"text-delta","delta":"You can "}
data: {"type":"text-delta","delta":"upgrade your "}
data: {"type":"text-delta","delta":"plan by..."}
data: {"type":"tool-call","toolCallId":"tc_1","toolName":"getBillingInfo","args":{}}
data: {"type":"tool-result","toolCallId":"tc_1","result":"Current plan: Free"}
data: {"type":"text-delta","delta":"Based on your current plan..."}
data: {"type":"finish","finishReason":"stop"}
data: [DONE]

Response Headers

HeaderDescription
X-Prompt-IdUnique ID for this prompt (prm_xxx)
X-Chat-IdThe chat ID
X-Request-IdUnique request ID (req_xxx)

Errors

StatusErrorCause
400content requiredMessage content is empty
400No API key configuredAgent has no LLM API key set
404Chat not foundChat does not exist or is not accessible
429Concurrent limit reachedToo many concurrent requests for this agent's plan
DELETE /chats/:id/prompt

Cancel a running prompt for a chat.

Request

curl -X DELETE https://agent.smoothcodes.com/chats/chat_a1b2c3d4e5f6/prompt \
  -H "Authorization: Bearer USER_TOKEN"

Response 200 OK

{ "cancelled": 1 }
GET /chats/:id/prompts

List prompt history for a chat (last 50 prompts).

Request

curl https://agent.smoothcodes.com/chats/chat_a1b2c3d4e5f6/prompts \
  -H "Authorization: Bearer USER_TOKEN"

Response 200 OK

{
  "prompts": [
    {
      "id": "prm_a1b2c3d4e5f6",
      "content": "How do I upgrade?",
      "status": "done",
      "response": "You can upgrade by...",
      "createdAt": "2025-01-15T12:00:00.000Z",
      "updatedAt": "2025-01-15T12:00:05.000Z"
    }
  ]
}
DELETE /chats/:id

Delete (hide) a chat. Also cleans up any VPS session and R2 files in the background.

Request

curl -X DELETE https://agent.smoothcodes.com/chats/chat_a1b2c3d4e5f6 \
  -H "Authorization: Bearer USER_TOKEN"

Response 200 OK

{ "ok": true }

Chat Stateless

For one-off requests without creating a chat or managing history. No authentication required (but you must provide a config inline or the worker must have default env vars).

POST /chat

Send messages with inline config. No auth needed. Useful for quick integrations and testing.

Request

curl -N -X POST https://agent.smoothcodes.com/chat \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      { "role": "user", "content": "What is the capital of France?" }
    ],
    "config": {
      "llmProvider": "anthropic",
      "llmApiKey": "sk-ant-api03-your-key",
      "llmModel": "claude-haiku-4-5-20251001",
      "systemPrompt": "Be concise.",
      "maxSteps": 5
    }
  }'
FieldTypeRequiredDescription
messagesarrayYesArray of {role, content} messages
configobjectNoInline agent config (provider, key, model, etc.)

Response: SSE Stream

data: {"type":"text-delta","delta":"The capital"}
data: {"type":"text-delta","delta":" of France is Paris."}
data: {"type":"finish","finishReason":"stop"}
data: [DONE]
POST /chat/mock

Returns a mock SSE stream for testing your parser without using any LLM credits.

Request

curl -N -X POST https://agent.smoothcodes.com/chat/mock

Response: SSE Stream

data: {"type":"text-delta","delta":"This is "}
data: {"type":"text-delta","delta":"a mock response "}
data: {"type":"text-delta","delta":"for testing."}
data: {"type":"finish","finishReason":"stop"}
data: [DONE]

Agent Config Fields

These fields can be set when creating or updating an agent via POST /dashboard/configs or PUT /dashboard/configs/:id.

FieldTypeRequiredDefaultDescription
mcpUrlstringNonullMCP server URL (legacy, prefer mcpServers)
mcpTokenstringNonullAuthorization header for the MCP server
mcpServersarrayNo[]Multi-MCP: [{name, url, token?}]
llmProviderstringNoauto-detectanthropic, openai, google, or auto-detected from key prefix
llmApiKeystringNonullLLM provider API key (stored AES-GCM encrypted)
llmModelstringNoprovider defaultModel ID (e.g. claude-sonnet-4-20250514, gpt-4o)
llmBaseUrlstringNonullCustom base URL for OpenAI-compatible APIs (e.g. Groq, Together)
themeNamestringNo"Chat"Display name shown in the chat UI
themeColorstringNo"#6366f1"Accent color (hex)
themeLogostringNonullLogo URL for the chat widget
systemPromptstringNonullSystem prompt sent with every request
maxHistorynumber|nullNonullnull=unlimited, 0=stateless (only last message), N=sliding window of N messages
maxTokensnumber|nullNonullMaximum output tokens per response
temperaturenumber|nullNonullSampling temperature (0.0 to 2.0). Lower = more deterministic.
maxStepsnumber|nullNo15Maximum agentic loop iterations (tool call rounds)
maxBudgetUsdnumber|nullNonullMaximum budget per session in USD
webSearchbooleanNofalseEnable built-in web search tool
webhookUrlstring|nullNonullPre/post tool execution hooks. Receives JSON with event, toolName, args/result.
allowedOriginsstringNonullComma-separated allowed CORS origins

Multi-MCP Example

{
  "mcpServers": [
    { "name": "crm", "url": "https://crm-mcp.example.com/mcp", "token": "Bearer crm-token" },
    { "name": "billing", "url": "https://billing-mcp.example.com/mcp" },
    { "name": "internal", "url": "internal" }
  ]
}

Tools from all MCP servers are merged and available to the agent. Use "url": "internal" to include SmoothAgent's built-in admin tools.

Webhook Hooks

When webhookUrl is set, the agent sends HTTP POST requests before and after each tool call:

Pre-hook (before tool execution):

{
  "event": "tool.before",
  "toolName": "searchDatabase",
  "toolCallId": "tc_123",
  "args": { "query": "user billing" }
}

Return {"allow": false, "reason": "Blocked"} to prevent tool execution.

Post-hook (after tool execution, fire-and-forget):

{
  "event": "tool.after",
  "toolName": "searchDatabase",
  "toolCallId": "tc_123",
  "result": { "rows": [...] }
}

Stream Format (SSE)

All streaming endpoints return text/event-stream with JSON payloads. Each line is prefixed with data: .

Standard Event Types

typeFieldsDescription
text-delta{ delta }Partial text token from the model
thinking-delta{ delta }Extended thinking/reasoning token
reasoning-delta{ delta }Alias for thinking-delta (some providers)
tool-call{ toolCallId, toolName, args? }Agent is calling a tool
tool-input-start{ toolCallId, toolName }Alias for tool-call (Vercel AI SDK format)
tool-result{ toolCallId, result }Tool returned a result
tool-output-available{ toolCallId, result }Alias for tool-result (Vercel AI SDK format)
tool-output-error{ toolCallId, errorText }Tool call failed
error{ errorText }Stream-level error
finish{ finishReason }Stream completed: stop, length, or error

The stream always ends with:

data: [DONE]

SSE Parsing Example (Browser)

async function streamPrompt(chatId, content, token) {
  const res = await fetch('https://agent.smoothcodes.com/chats/' + chatId + '/prompt', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token,
    },
    body: JSON.stringify({ content }),
  });

  const promptId = res.headers.get('X-Prompt-Id');
  const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
  let buffer = '';
  let fullText = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += value;
    const lines = buffer.split('\n');
    buffer = lines.pop(); // keep incomplete line in buffer

    for (const line of lines) {
      if (!line.startsWith('data: ')) continue;
      const payload = line.slice(6);
      if (payload === '[DONE]') break;

      const event = JSON.parse(payload);
      switch (event.type) {
        case 'text-delta':
          fullText += event.delta;
          document.getElementById('output').textContent = fullText;
          break;
        case 'tool-call':
          console.log('Tool called:', event.toolName);
          break;
        case 'tool-result':
          console.log('Tool result:', event.result);
          break;
        case 'error':
          console.error('Error:', event.errorText);
          break;
        case 'finish':
          console.log('Finished:', event.finishReason);
          break;
      }
    }
  }

  return { text: fullText, promptId };
}

Claude Code Path (Additional Events)

When the agent uses a Claude Code provider (sk-ant-oat01-* key), additional event types appear:

typeFieldsDescription
assistant{ message }Accumulated message with content blocks
tool_use{ name, input }Standalone tool call (name and input)
tool_result{ content }Standalone tool result
result{ session_id, usage, cost }Final event with session_id, usage stats, and cost
session{ session_id }Session ID for resuming conversation

The Worker translates Claude Code events into the standard format (text-delta, tool-call, etc.), so the SDK and parser work seamlessly with both paths.

Error Codes

All errors return JSON: { "error": "message" }

StatusMeaningCommon CausesSolution
400 Bad Request Missing required fields, invalid JSON, invalid plan name, nothing to update Check your request body matches the endpoint's requirements
401 Unauthorized Missing Authorization header, expired token, invalid JWT signature Include a valid Bearer token. Re-login if expired.
403 Forbidden Origin not allowed, email not verified, admin token required for endpoint Check CORS settings, verify email, or use correct token type
404 Not Found Resource does not exist or not owned by authenticated user Verify the ID is correct and belongs to the authenticated account
409 Conflict Email already registered (signup) Use login instead, or use a different email
413 Payload Too Large Request body exceeds 20MB Reduce file sizes or split into multiple requests
429 Too Many Requests Rate limit exceeded (30 req/min per IP), concurrent limit reached, login lockout Wait and retry with backoff. Upgrade plan for higher limits.
500 Internal Server Error Unexpected server error, database error, upstream LLM failure Retry the request. If persistent, check the agent's config.
502 Bad Gateway MCP server unreachable, VPS down, upstream provider timeout Check MCP server health via /dashboard/mcp-health

Rate Limits

Request Rate Limits

ContextLimitWindow
Unauthenticated requests30 req/min per IP1 minute (sliding)
Authenticated requestsNo rate limitN/A (concurrency limits apply)
Login attempts (per email)5 attempts15 min lockout after 5 failures

Concurrency Limits (per agent)

PlanIncludedHard CapOverage
Free11Blocked (upgrade to Pro)
Pro10100Metered via Stripe
Business1001,000Metered via Stripe
Enterprise1,00010,000Metered via Stripe

Rate Limit Headers

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

Retry Example

async function fetchWithRetry(url, opts, maxRetries = 3) {
  for (let i = 0; i <= maxRetries; i++) {
    const res = await fetch(url, opts);

    // Don't retry client errors (4xx) except 429
    if (res.status >= 400 && res.status < 500 && res.status !== 429) {
      return res;
    }

    if (res.ok) return res;

    if (res.status === 429) {
      // Use the Retry-After header if provided, otherwise exponential backoff
      const retryAfter = res.headers.get('Retry-After');
      const waitMs = retryAfter
        ? parseInt(retryAfter) * 1000
        : 1000 * Math.pow(2, i);
      await new Promise(r => setTimeout(r, waitMs));
      continue;
    }

    // 5xx: retry with exponential backoff
    if (i < maxRetries) {
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
      continue;
    }

    return res;
  }
}

Providers

SmoothAgent auto-detects the provider from the API key prefix, or you can set llmProvider explicitly.

ProviderKey PrefixDefault ModelNotes
Anthropicsk-ant-api*claude-sonnet-4-20250514Claude models, extended thinking support
OpenAIsk-gpt-4oGPT models, function calling
GoogleAIza*gemini-2.0-flashGemini models
Claude Codesk-ant-oat01-*Agent modePersistent sessions, real tool execution, workspace
Customanyper configSet llmBaseUrl for OpenAI-compatible APIs

Custom Provider Example (Groq)

curl -X POST https://agent.smoothcodes.com/dashboard/configs \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer DEV_TOKEN" \
  -d '{
    "llmProvider": "openai",
    "llmApiKey": "gsk_your_groq_key",
    "llmModel": "llama-3.3-70b-versatile",
    "llmBaseUrl": "https://api.groq.com/openai/v1",
    "themeName": "Groq Agent"
  }'

Any provider with an OpenAI-compatible chat completions API works with llmBaseUrl. Examples: Groq, Together, Fireworks, Ollama, LM Studio.

Claude Code Agent Mode

When using an OAuth subscription token (sk-ant-oat01-*), requests are routed to a VPS running ClaudeAPI in Docker containers:

Integration Examples

Node.js Full Example

import { SmoothAgent } from 'smoothagent';

// 1. Admin creates an agent and generates a user token
const admin = new SmoothAgent({
  token: process.env.DEV_TOKEN,
  workerUrl: 'https://agent.smoothcodes.com',
});

// Create an agent
const { id: configId } = await admin.createAgent({
  llmProvider: 'anthropic',
  llmApiKey: process.env.ANTHROPIC_API_KEY,
  systemPrompt: 'You are a customer support agent for Acme Inc.',
  themeName: 'Acme Support',
});

// Generate a user token for the end-user
const { token: userToken } = await admin.createUser('user_jane', { configId });

// 2. End-user uses their token to chat
const user = new SmoothAgent({
  token: userToken,
  workerUrl: 'https://agent.smoothcodes.com',
});

const chat = await user.createChat('Help with my order');

const result = await user.prompt(chat.id, 'Where is my order #12345?', {
  onDelta: (delta) => process.stdout.write(delta),
  onToolCall: ({ toolName }) => console.log('\n[Tool]', toolName),
  onToolResult: ({ result }) => console.log('[Result]', result),
  onFinish: ({ text, reason }) => console.log('\n[Done]', reason),
});

console.log('\nFull response:', result.text);
console.log('Usage:', result.usage);

Browser ES Module

<script type="module">
import { SmoothAgent } from 'https://agent.smoothcodes.com/js/sdk.js';

const agent = new SmoothAgent({ token: 'USER_TOKEN' });

const chat = await agent.createChat();

for await (const event of agent.stream(chat.id, 'Hello!')) {
  if (event.type === 'text-delta') {
    document.body.textContent += event.delta;
  }
}
</script>

React Component

import { useState, useCallback } from 'react';
import { SmoothAgent } from 'smoothagent';

const agent = new SmoothAgent({ token: 'USER_TOKEN' });

function Chat() {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const [chatId, setChatId] = useState(null);
  const [streaming, setStreaming] = useState(false);

  const send = useCallback(async () => {
    if (!input.trim() || streaming) return;

    let currentChatId = chatId;
    if (!currentChatId) {
      const chat = await agent.createChat();
      currentChatId = chat.id;
      setChatId(currentChatId);
    }

    setMessages(prev => [...prev, { role: 'user', content: input }]);
    setMessages(prev => [...prev, { role: 'assistant', content: '' }]);
    setInput('');
    setStreaming(true);

    await agent.prompt(currentChatId, input, {
      onDelta: (delta) => {
        setMessages(prev => {
          const updated = [...prev];
          const last = updated[updated.length - 1];
          updated[updated.length - 1] = { ...last, content: last.content + delta };
          return updated;
        });
      },
      onFinish: () => setStreaming(false),
      onError: (err) => {
        console.error(err);
        setStreaming(false);
      },
    });
  }, [input, chatId, streaming]);

  return (
    <div>
      {messages.map((m, i) => (
        <div key={i} className={m.role}>
          <strong>{m.role}:</strong> {m.content}
        </div>
      ))}
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        onKeyDown={e => e.key === 'Enter' && send()}
        placeholder="Type a message..."
      />
      <button onClick={send} disabled={streaming}>Send</button>
    </div>
  );
}

cURL Step-by-Step

# 1. Login
TOKEN=$(curl -s -X POST https://agent.smoothcodes.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"pass123"}' \
  | jq -r '.token')

# 2. List your agents
curl -s https://agent.smoothcodes.com/dashboard/configs \
  -H "Authorization: Bearer $TOKEN" | jq '.configs[].id'

# 3. Generate a user token for an agent
USER_TOKEN=$(curl -s -X POST https://agent.smoothcodes.com/dashboard/configs/CONFIG_ID/token \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"userId":"user_test"}' \
  | jq -r '.token')

# 4. Create a chat
CHAT_ID=$(curl -s -X POST https://agent.smoothcodes.com/chats \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $USER_TOKEN" \
  -d '{"title":"Test chat"}' \
  | jq -r '.id')

# 5. Send a prompt (stream to terminal)
curl -N -X POST https://agent.smoothcodes.com/chats/$CHAT_ID/prompt \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $USER_TOKEN" \
  -d '{"content":"Hello! What can you help me with?"}'