AI Teammate — Developer Integration Guide
This guide is designed for AI coding assistants (Claude Code, Cursor, GitHub Copilot, etc.) to help developers build custom apps powered by AI Teammate agents. Feed this document to your coding assistant when building Flutter, React Native, Swift, or any client app.
Overview
AI Teammate is an AI agent platform where users create personalized AI agents. Each agent can be deployed as a web app and also integrated into custom native/web apps via REST API.
Architecture
Your Custom App (Flutter, React Native, etc.)
↓ HTTPS
AI Teammate Platform API (https://ai-teammate.net/api)
├─ /agents ← Agent CRUD (API Key auth)
├─ /shared/{share_code}/chat ← Chat with agent
├─ /end-user/auth/{agent_id}/… ← End-user authentication
├─ /bookmarks/end-user/{agent_id} ← Bookmark CRUD
├─ /apps ← App deploy & publish
├─ /apps/slug/{slug} ← App metadata (public)
└─ /webhook/{token} ← Webhook integration
Base URL
https://ai-teammate.net/api
Authentication
Three auth methods:
| Method | Header | Scope | How to get |
|---|---|---|---|
| API Key (Global) | Authorization: Bearer at_xxxxx | Agent CRUD, App deploy, Chat | Settings → API Keys |
| API Key (App-scoped) | Authorization: Bearer at_xxxxx | Chat, Bookmarks for specific app | Auto-generated on App deploy |
| User JWT | Authorization: Bearer eyJ... | Same as Global API Key | Login at ai-teammate.net |
| End-user JWT | Authorization: Bearer eyJ... | Chat history, Bookmarks | End-user auth endpoints |
| None | — | Public endpoints | Rate-limited by IP |
App-scoped API Key is recommended for deployed apps (web, Flutter, React Native). It is auto-generated when you create a DeployedApp and enables per-app usage monitoring.
API Key Management
Get an API Key
- Sign up at https://ai-teammate.net
- Go to Settings → API tab
- Create a new API key
- Copy the key (shown only once, starts with
at_)
API Key Endpoints
Create API Key
POST /keys
Authorization: Bearer {user_jwt_or_api_key}
{ "name": "My Flutter App" }
→ {
"id": string,
"name": "My Flutter App",
"key": "at_xxxxxxxxxxxxxxxx", // ← Only shown once!
"created_at": datetime
}
List API Keys
GET /keys
Authorization: Bearer {user_jwt_or_api_key}
→ [{ "id", "name", "created_at" }] // key is NOT returned
Delete API Key
DELETE /keys/{key_id}
Authorization: Bearer {user_jwt_or_api_key}
→ { "message": "API key deleted" }
Agent Management (API Key Required)
With an API key, you can programmatically create and manage agents.
Create Agent
POST /agents
Authorization: Bearer at_xxxxx
{
"name": "Recipe Assistant", // Required (1-100 chars)
"description": "Recommends recipes", // Optional
"personality": "friendly", // friendly, professional, humorous, caring, witty
"tone": "casual", // casual, formal, playful
"skills": ["core"], // Base skill set
"enabled_skills": ["weather", "bookmark", "naver_search"], // Platform skills to activate
"system_prompt": "You are a recipe expert..." // Custom system prompt
}
→ {
"id": "uuid",
"name": "Recipe Assistant",
"owner_id": "uuid",
"enabled_skills": [...],
"status": "active",
"share_code": null, // Created when you publish/share
"created_at": datetime
}
Available enabled_skills
| Skill ID | Description |
|---|---|
weather | Real-time weather lookup |
naver_search | Naver web search |
tavily | Tavily web search (English-optimized) |
bookmark | Save/manage bookmarks |
education | Quiz generation, flashcards |
rainbowstock | Stock price lookup |
image_gen | AI image generation (requires OpenAI key) |
code_exec | Python code execution |
web_scrape | Web page content extraction |
reminder | Scheduled reminders |
papers | Academic paper search |
List My Agents
GET /agents
Authorization: Bearer at_xxxxx
→ [{ "id", "name", "description", "status", "enabled_skills", ... }]
Get Agent Details
GET /agents/{agent_id}
Authorization: Bearer at_xxxxx
→ { "id", "name", "description", "personality", "tone", "system_prompt", "enabled_skills", ... }
Update Agent
PUT /agents/{agent_id}
Authorization: Bearer at_xxxxx
{
"name": "Updated Name",
"system_prompt": "New prompt...",
"enabled_skills": ["weather", "bookmark"]
}
→ { ... updated agent ... }
Delete Agent
DELETE /agents/{agent_id}
Authorization: Bearer at_xxxxx
→ { "message": "Agent archived" }
AI-Powered Agent Setup
POST /agents/ai-setup
Authorization: Bearer at_xxxxx
{ "description": "A cooking assistant that recommends recipes based on fridge ingredients" }
→ {
"name": "Fridge Chef",
"personality": "friendly",
"tone": "casual",
"system_prompt": "You are a cooking assistant...",
"enabled_skills": ["naver_search", "bookmark"],
"description": "냉장고 재료로 맛있는 요리를 추천합니다"
}
Use the AI setup response to create an agent: take the returned fields and pass them to
POST /agents.
App Deployment (API Key Required)
After creating an agent, deploy it as a web app.
Create App
POST /apps
Authorization: Bearer at_xxxxx
{
"agent_id": "uuid",
"slug": "my-recipe-app", // Optional, auto-generated if omitted
"name": "Fridge Chef App",
"tagline": "Turn your fridge into a feast"
}
→ {
"id": string,
"slug": "my-recipe-app",
"status": "draft",
"api_key": "at_xxxxx..." // ← App-scoped API Key (shown only once!)
}
App-scoped API Key: Auto-generated when you create an app. Use this key in your client app for per-app usage tracking. The key is shown only at creation time.
Configure App
PATCH /apps/{app_id}
Authorization: Bearer at_xxxxx
{
"primary_color": "#10b981",
"theme": "dark",
"icon_url": "https://...",
"app_host_skills": ["file_upload", "bookmarks", "quick_actions"],
"quick_actions": [
{ "label": "What can I cook?", "message": "냉장고에 뭐 있는지 물어봐줘", "icon": "🍳" },
{ "label": "Random recipe", "message": "아무 레시피 하나 추천해줘", "icon": "🎲" }
]
}
Publish App
POST /apps/{app_id}/publish
Authorization: Bearer at_xxxxx
→ {
"status": "published",
"share_code": "ABC123xyz", // ← Use this for Chat API
"published_at": datetime
}
Your app is now live at https://app.ai-teammate.net/{slug}
Full Programmatic Flow
import requests
API = "https://ai-teammate.net/api"
KEY = "at_your_api_key"
H = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
# 1. Create agent
agent = requests.post(f"{API}/agents", headers=H, json={
"name": "Fridge Chef",
"description": "Recommends recipes from fridge ingredients",
"personality": "friendly",
"enabled_skills": ["naver_search", "bookmark", "weather"],
"system_prompt": "You are a friendly Korean cooking assistant. When users tell you what ingredients they have, suggest creative recipes."
}).json()
# 2. Create app
app = requests.post(f"{API}/apps", headers=H, json={
"agent_id": agent["id"],
"slug": "fridge-chef",
"name": "Fridge Chef",
}).json()
# 3. Configure
requests.patch(f"{API}/apps/{app['id']}", headers=H, json={
"primary_color": "#F59E0B",
"app_host_skills": ["bookmarks", "quick_actions"],
"quick_actions": [
{"label": "🥗 What can I cook?", "message": "냉장고 재료로 뭐 만들 수 있어?"},
{"label": "🎲 Surprise me", "message": "아무 레시피 추천해줘"},
],
})
# 4. Publish
result = requests.post(f"{API}/apps/{app['id']}/publish", headers=H).json()
share_code = result["share_code"]
print(f"App live at: https://app.ai-teammate.net/fridge-chef")
print(f"Share code for Chat API: {share_code}")
# 5. Chat with the agent
response = requests.post(f"{API}/shared/{share_code}/chat", json={
"message": "냉장고에 닭가슴살, 양파, 간장이 있어"
}).json()
print(response["response"])
Quick Start: Flutter Recipe App Example
Here's how to build a "Fridge Recipe" app that uses an AI Teammate agent to recommend recipes based on selected ingredients.
Step 1: Get your share_code
The share_code is a unique identifier for your agent's chat endpoint. There are 3 ways to get it:
Option A: Via Studio UI
- Sign up at https://ai-teammate.net → Create an agent
- Agent Settings → Deploy tab → Publish
- Call
GET /apps/slug/{your-slug}→share_codeis in the response
Option B: Via API Key (Programmatic)
# Create agent
curl -X POST https://ai-teammate.net/api/agents \
-H "Authorization: Bearer at_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "My Agent", "enabled_skills": ["weather", "bookmark"]}'
# → returns { "id": "agent-uuid", ... }
# Create & publish app
curl -X POST https://ai-teammate.net/api/apps \
-H "Authorization: Bearer at_your_api_key" \
-H "Content-Type: application/json" \
-d '{"agent_id": "agent-uuid", "slug": "my-app"}'
# → returns { "id": "app-uuid", ... }
curl -X POST https://ai-teammate.net/api/apps/app-uuid/publish \
-H "Authorization: Bearer at_your_api_key"
# → returns { "share_code": "ABC123xyz", ... }
Option C: From App Metadata (Public, no auth)
curl https://ai-teammate.net/api/apps/slug/my-app
# → { "share_code": "ABC123xyz", "agent_id": "4b4b...", ... }
share_code— Chat API에 사용 (POST /shared/{share_code}/chat)agent_id— Auth, Bookmarks API에 사용 (/end-user/auth/{agent_id}/...,/bookmarks/end-user/{agent_id})둘 다
GET /apps/slug/{slug}한번으로 얻을 수 있습니다. 앱을 publish하면 slug이 생기고, 이 slug으로 모든 키 값을 조회합니다.
Step 2: Basic chat integration
// lib/services/ai_agent.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class AIAgent {
static const _baseUrl = 'https://ai-teammate.net/api';
final String shareCode;
String? _endUserToken;
AIAgent({required this.shareCode});
void setToken(String token) => _endUserToken = token;
/// Send a message to the agent and get a response
Future<String> chat(String message, {String? image}) async {
final body = {
'message': message,
if (_endUserToken != null) 'end_user_token': _endUserToken,
if (image != null) 'image': image, // base64 data URL for vision
};
final res = await http.post(
Uri.parse('$_baseUrl/shared/$shareCode/chat'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
);
if (res.statusCode == 200) {
final data = jsonDecode(res.body);
return data['response'] ?? '';
} else if (res.statusCode == 401) {
throw AuthRequiredException();
} else if (res.statusCode == 429) {
throw RateLimitException();
} else {
throw Exception('Chat failed: ${res.statusCode}');
}
}
/// Get chat history (requires end-user auth)
Future<List<ChatMessage>> getHistory() async {
final res = await http.get(
Uri.parse('$_baseUrl/shared/$shareCode/history'),
headers: {
if (_endUserToken != null) 'Authorization': 'Bearer $_endUserToken',
},
);
if (res.statusCode == 200) {
final data = jsonDecode(res.body);
return (data['messages'] as List)
.map((m) => ChatMessage(role: m['role'], content: m['content']))
.toList();
}
return [];
}
}
class ChatMessage {
final String role;
final String content;
ChatMessage({required this.role, required this.content});
}
class AuthRequiredException implements Exception {}
class RateLimitException implements Exception {}
Step 3: Use it in your recipe screen
// lib/screens/recipe_screen.dart
// Option A: Use AITeammate SDK (recommended — auto-resolves share_code & agent_id from slug)
final agent = await AITeammate.fromSlug('my-recipe-app');
// Option B: Use AIAgent directly with share_code
// final agent = AIAgent(shareCode: 'ABC123xyz'); // from GET /apps/slug/{slug}
// User selects ingredients: chicken, garlic, soy sauce
final ingredients = ['chicken', 'garlic', 'soy sauce'];
final prompt = '냉장고에 ${ingredients.join(", ")}이 있어. 이 재료로 만들 수 있는 레시피 추천해줘.';
final response = await agent.chat(prompt);
// Agent responds with recipe recommendations using its skills (search, etc.)
API Reference
1. Chat API
Send Message
POST /shared/{share_code}/chat
Content-Type: application/json
Request:
{
"message": string, // Required (1-10000 chars)
"end_user_token": string?, // JWT from end-user auth
"image": string?, // Base64 data URL for vision
"channel": string? // "shared_link" | "embed"
}
Response 200:
{
"response": string, // Agent's reply
"remaining": int, // Messages left (-1 = unlimited)
"daily_remaining": int // Daily messages left (-1 = unlimited)
}
Errors:
401 — { "error": "end_user_auth_required" } // Auth needed
403 — { "error": "owner_insufficient_credits" }
404 — Share not found
410 — Share link expired
429 — Rate limit exceeded or daily limit reached
Get Chat History
GET /shared/{share_code}/history
Authorization: Bearer {end_user_token} // Required
Response 200:
{
"messages": [
{ "role": "user", "content": "...", "created_at": "..." },
{ "role": "assistant", "content": "...", "created_at": "..." }
]
}
Upload Document (RAG)
POST /shared/{share_code}/documents/upload
Authorization: Bearer {end_user_token}
Content-Type: multipart/form-data
file: binary (PDF, TXT, MD, DOCX, CSV — max 10MB)
Response 200:
{
"id": string,
"filename": string,
"chunk_count": int,
"size_bytes": int
}
Note: Uploaded documents are used as RAG context — the agent can reference them in subsequent chat messages.
2. End-User Authentication
All auth endpoints are scoped to an agent. The {agent_id} can be obtained from GET /apps/slug/{slug}.
Register
POST /end-user/auth/{agent_id}/register
{
"email": string,
"name": string,
"agreed_terms": true,
"agreed_privacy": true,
"source": "my_flutter_app" // Track signup source
}
→ { "status": "verification_sent" }
Verify Email
POST /end-user/auth/{agent_id}/verify
{ "email": string, "code": string }
→ { "status": "verified", "needs_password": true }
Set Password
POST /end-user/auth/{agent_id}/set-password
{ "email": string, "password": string }
→ { "token": "eyJ...", "end_user": { "id", "email", "name" } }
Login
POST /end-user/auth/{agent_id}/login
{ "email": string, "password": string }
→ { "token": "eyJ...", "end_user": { "id", "email", "name" } }
Validate Token
GET /end-user/auth/{agent_id}/validate
Authorization: Bearer {token}
→ { "valid": true, "end_user": { ... } }
Google OAuth
GET /end-user/auth/{agent_id}/google?redirect_uri={uri}&return_url={url}
→ { "url": "https://accounts.google.com/..." }
// After Google auth:
POST /end-user/auth/google/callback
{ "code": string, "agent_id": string, "redirect_uri": string }
→ { "token": "eyJ...", "end_user": { ... }, "is_new": boolean }
Token lifetime: 30 days. Store securely (Keychain/SharedPreferences).
3. Bookmarks API (Unified)
End-user personal bookmarks, scoped per agent. Uses the unified Bookmark system with auto metadata extraction.
Save Bookmark
POST /bookmarks/end-user/{agent_id}
Authorization: Bearer {end_user_token}
{ "url": string, "title": string?, "tags": string[]? }
→ { "status": "created", "bookmark": { "id", "title", "url", "tags", "og_image", "category", "description", ... } }
OG metadata (title, image, description) and category are auto-extracted from the URL.
List My Bookmarks
GET /bookmarks/end-user/{agent_id}?page=1&page_size=20&tag=recipes&search=chicken
Authorization: Bearer {end_user_token}
→ {
"bookmarks": [...],
"total": int,
"page": int,
"has_more": boolean
}
Get Categories / Tags
GET /bookmarks/end-user/{agent_id}/categories
GET /bookmarks/end-user/{agent_id}/tags
Authorization: Bearer {end_user_token}
→ { "categories": ["recipes", "dev", ...] }
→ { "tags": ["korean", "chicken", ...] }
Update Bookmark
PATCH /bookmarks/end-user/{agent_id}/{bookmark_id}
Authorization: Bearer {end_user_token}
{ "title": string?, "tags": string[]?, "category": string?, "reminder_at": datetime? }
→ { BookmarkResponse }
Delete Bookmark
DELETE /bookmarks/end-user/{agent_id}/{bookmark_id}
Authorization: Bearer {end_user_token}
→ { "status": "deleted" }
4. App Metadata (Public)
Get App by Slug
GET /apps/slug/{slug}
→ {
"id": string,
"slug": string,
"name": string,
"agent_id": string,
"agent_name": string,
"agent_avatar_url": string?,
"share_code": string, // ← Use this for chat API
"primary_color": "#10b981",
"theme": "dark" | "light",
"icon_url": string?,
"background_image_url": string?,
"skills": ["bookmarks", "order", ...],
"quick_actions": [{ "label", "message", "icon" }],
"enabled_skills": ["weather", "naver_search", ...]
}
Use
share_codefrom this response for the Chat API. Useagent_idfor Auth and Bookmarks APIs. Useskillsto decide which UI features to show. Usequick_actionsto render quick action buttons.
5. Webhook Integration
For server-to-server integration (chatbots, Slack, custom backends).
POST /webhook/{webhook_token}
{
"message": string,
"user_id": string?, // Your external user ID
"end_user_token": string?, // For authenticated context
"metadata": object?
}
→ {
"response": string,
"agent_id": string,
"agent_name": string
}
Get
webhook_tokenfrom Agent Settings → API tab in the studio.
Skill Response Parsing
When the agent has UI skills enabled (order, reservation, quick_actions), its responses may contain special blocks that your app can parse and render as rich UI.
Block Format
Some text explanation...
:::skill:order
{
"view": "menu",
"title": "Today's Menu",
"items": [
{ "id": "1", "name": "Chicken Teriyaki", "price": 12000, "description": "..." }
],
"total": 0,
"currency": "KRW"
}
:::
Parsing Example (Dart)
class SkillBlock {
final String skillName;
final Map<String, dynamic> data;
SkillBlock({required this.skillName, required this.data});
}
List<SkillBlock> parseSkillBlocks(String content) {
final regex = RegExp(r':::skill:(\w+)\n([\s\S]*?):::', multiLine: true);
return regex.allMatches(content).map((match) {
return SkillBlock(
skillName: match.group(1)!,
data: jsonDecode(match.group(2)!.trim()),
);
}).toList();
}
String getTextContent(String content) {
return content.replaceAll(RegExp(r':::skill:\w+\n[\s\S]*?:::'), '').trim();
}
Skill Block Types
Order (:::skill:order)
{
"view": "menu" | "cart" | "confirm",
"title": string,
"items": [{ "id", "name", "price", "qty?", "description?" }],
"total": number,
"currency": "KRW"
}
Reservation (:::skill:reservation)
{
"view": "form" | "confirm" | "cancel",
"title": string,
"date": "2026-03-25",
"time": "14:00",
"party_size": 2,
"name": string,
"status": "confirmed" | "pending" | "cancelled"
}
Quick Actions (:::skill:quick_actions)
[
{ "label": "View Menu", "message": "메뉴 보여줘", "icon": "🍔" },
{ "label": "My Orders", "message": "주문 내역", "icon": "📋" }
]
Integration Patterns
Pattern 1: Chat-Only (Simplest)
Use the agent as a conversational backend. No auth needed.
App → POST /shared/{share_code}/chat → Agent response
Best for: Simple Q&A apps, information kiosks, demo prototypes.
Pattern 2: Chat + Auth (Personalized)
Add end-user authentication for personalized conversations with history.
App → POST /end-user/auth/{agent_id}/login → Get token
App → POST /shared/{share_code}/chat (with end_user_token) → Personalized response
App → GET /shared/{share_code}/history → Load previous messages
Best for: Apps where users return and expect continuity.
Pattern 3: Chat + Bookmarks (Content Curation)
End-users can save and organize content recommended by the agent.
Agent recommends a URL → App calls POST /bookmarks/end-user/{agent_id}
User opens bookmarks tab → GET /bookmarks/end-user/{agent_id}
Best for: Recipe apps, learning apps, content discovery.
Pattern 4: Rich UI with Skill Blocks
Parse agent responses for structured data and render custom UI cards.
User: "메뉴 보여줘"
Agent response: "Here's today's menu:\n:::skill:order\n{...}\n:::"
App: Parse :::skill:order block → Render MenuCard widget
User taps item → Send "치킨 테리야키 1개 주문" → Agent confirms
Best for: E-commerce, food ordering, reservation apps.
Pattern 5: Webhook (Server-to-Server)
Integrate the agent into your backend workflow.
Your Server → POST /webhook/{token} → Agent response → Your Server processes
Best for: Slack bots, Telegram bots, automated workflows, email responders.
Rate Limits & Quotas
| Endpoint | Scope |
|---|---|
| Chat (shared) | Per IP per share_code |
| Chat (daily) | Per end-user or IP (configurable) |
| Auth endpoints | Per IP |
| Bookmarks | Per end-user |
| Webhook | Per webhook token |
Specific rate limit values are not disclosed. Exceeding limits returns HTTP 429.
Error Handling
All errors follow this format:
{
"detail": string | { "error": string, "message": string }
}
Common error codes:
| Status | Error | Action |
|---|---|---|
| 401 | end_user_auth_required | Redirect to login flow |
| 403 | owner_insufficient_credits | Show "service unavailable" |
| 404 | Share/Agent not found | Check share_code/agent_id |
| 410 | Share link expired | Contact agent owner |
| 429 | rate_limit_exceeded | Retry after cooldown |
| 429 | daily_limit_reached | Show "try again tomorrow" |
Complete Flutter Integration Example
// lib/services/ai_teammate.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class AITeammate {
static const baseUrl = 'https://ai-teammate.net/api';
final String shareCode;
final String agentId;
String? _token;
AITeammate({required this.shareCode, required this.agentId});
/// Initialize from app slug
static Future<AITeammate> fromSlug(String slug) async {
final res = await http.get(Uri.parse('$baseUrl/apps/slug/$slug'));
final data = jsonDecode(res.body);
final instance = AITeammate(
shareCode: data['share_code'],
agentId: data['agent_id'],
);
// Restore saved token
final prefs = await SharedPreferences.getInstance();
instance._token = prefs.getString('ai_teammate_token');
return instance;
}
bool get isAuthenticated => _token != null;
// ── Auth ──────────────────────────────────────────
Future<void> register(String email, String name) async {
await _post('/end-user/auth/$agentId/register', {
'email': email,
'name': name,
'agreed_terms': true,
'agreed_privacy': true,
});
}
Future<bool> verify(String email, String code) async {
final data = await _post('/end-user/auth/$agentId/verify', {
'email': email,
'code': code,
});
return data['needs_password'] == true;
}
Future<Map<String, dynamic>> login(String email, String password) async {
final data = await _post('/end-user/auth/$agentId/login', {
'email': email,
'password': password,
});
await _saveToken(data['token']);
return data['end_user'];
}
Future<Map<String, dynamic>> setPassword(String email, String password) async {
final data = await _post('/end-user/auth/$agentId/set-password', {
'email': email,
'password': password,
});
await _saveToken(data['token']);
return data['end_user'];
}
Future<void> logout() async {
_token = null;
final prefs = await SharedPreferences.getInstance();
await prefs.remove('ai_teammate_token');
}
// ── Chat ──────────────────────────────────────────
Future<String> chat(String message, {String? image}) async {
final body = {
'message': message,
if (_token != null) 'end_user_token': _token,
if (image != null) 'image': image,
};
final data = await _post('/shared/$shareCode/chat', body);
return data['response'] ?? '';
}
Future<List<Map<String, dynamic>>> getHistory() async {
final data = await _get('/shared/$shareCode/history');
return List<Map<String, dynamic>>.from(data['messages'] ?? []);
}
// ── Bookmarks ─────────────────────────────────────
Future<Map<String, dynamic>> saveBookmark(String url, {String? title}) async {
return await _post('/bookmarks/end-user/$agentId', {
'url': url,
if (title != null) 'title': title,
}, auth: true);
}
Future<List<Map<String, dynamic>>> getMyBookmarks({int page = 1}) async {
final data = await _get('/bookmarks/end-user/$agentId?page=$page', auth: true);
return List<Map<String, dynamic>>.from(data['bookmarks'] ?? []);
}
Future<void> deleteBookmark(String id) async {
await _delete('/bookmarks/end-user/$agentId/$id');
}
// ── Helpers ───────────────────────────────────────
Future<Map<String, dynamic>> _post(String path, Map body, {bool auth = false}) async {
final res = await http.post(
Uri.parse('$baseUrl$path'),
headers: {
'Content-Type': 'application/json',
if (auth && _token != null) 'Authorization': 'Bearer $_token',
},
body: jsonEncode(body),
);
if (res.statusCode >= 400) throw _handleError(res);
return jsonDecode(res.body);
}
Future<Map<String, dynamic>> _get(String path, {bool auth = false}) async {
final res = await http.get(
Uri.parse('$baseUrl$path'),
headers: {
if (auth && _token != null) 'Authorization': 'Bearer $_token',
},
);
if (res.statusCode >= 400) throw _handleError(res);
return jsonDecode(res.body);
}
Future<void> _delete(String path) async {
final res = await http.delete(
Uri.parse('$baseUrl$path'),
headers: {
if (_token != null) 'Authorization': 'Bearer $_token',
},
);
if (res.statusCode >= 400) throw _handleError(res);
}
Exception _handleError(http.Response res) {
try {
final data = jsonDecode(res.body);
final detail = data['detail'];
if (detail is Map) return Exception(detail['message'] ?? detail['error']);
return Exception(detail?.toString() ?? 'Request failed');
} catch (_) {
return Exception('Request failed: ${res.statusCode}');
}
}
Future<void> _saveToken(String token) async {
_token = token;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('ai_teammate_token', token);
}
}
Usage in a Recipe App
// Initialize
final agent = await AITeammate.fromSlug('my-recipe-agent');
// Login (if needed)
if (!agent.isAuthenticated) {
await agent.register('user@example.com', 'John');
// User enters verification code from email
await agent.verify('user@example.com', '123456');
await agent.setPassword('user@example.com', 'mypassword');
}
// Chat with ingredient selection
final selectedIngredients = ['chicken', 'garlic', 'onion', 'soy sauce'];
final response = await agent.chat(
'I have these ingredients: ${selectedIngredients.join(", ")}. '
'Suggest 3 recipes I can make.',
);
// Parse skill blocks for rich UI
final blocks = parseSkillBlocks(response);
final textContent = getTextContent(response);
// Save a recipe as bookmark
await agent.saveBookmark(
'https://example.com/recipe/chicken-teriyaki',
title: 'Chicken Teriyaki Recipe',
);
// Load saved recipes
final myRecipes = await agent.getMyBookmarks();
Environment Setup
For Development
BASE_URL=https://ai-teammate.net/api
For Self-Hosted
If running AI Teammate on your own server:
BASE_URL=https://your-domain.com/api
CORS
The API uses dynamic CORS based on registered apps:
https://ai-teammate.netand platform domains — always allowedhttp://localhost:*— allowed for development- DeployedApp custom_domain — automatically allowed when you register your app's domain in Deploy settings
- Trusted platforms (
.vercel.app,.netlify.app,.pages.dev, etc.) — domain verification is automatic
For native apps (Flutter, React Native), CORS does not apply — use your App-scoped API Key for identification.
SSE Streaming Protocol
The /agents/{agent_id}/chat/stream and /shared/{share_code}/chat/stream endpoints return Server-Sent Events. Each event follows the format: data: {"type":"...", ...}\n\n
| type | Fields | Description |
|---|---|---|
text | content | Text chunk (streaming fragment) |
tool_start | name, args, side_effect | Tool execution started |
tool_end | name, status, result, side_effect | Tool execution completed |
meta | conversation_id | Conversation metadata |
done | tokens_input, tokens_output | Stream finished |
error | message | Error occurred |
JavaScript SSE Parser:
function createSSEParser() {
let buffer = "";
return {
feed(chunk) {
buffer += chunk;
const events = [];
const parts = buffer.split("\n\n");
buffer = parts.pop() || "";
for (const part of parts) {
for (const line of part.split("\n")) {
const trimmed = line.startsWith("data: ") ? line.slice(6) : line;
if (!trimmed) continue;
try {
const parsed = JSON.parse(trimmed);
if (parsed?.type) events.push(parsed);
} catch {}
}
}
return events;
},
};
}
OAuth External App Integration
External apps can use AI Teammate accounts for login without registering additional Google OAuth callback URIs. Use the return_to parameter:
- External app calls
GET /api/auth/google?return_to={callback_url} - User completes Google login
- Redirects to
ai-teammate.net/auth/google/callback(existing callback URI) - If
statecontainsreturn_to→ redirects to{callback_url}?token={jwt} - External app calls
GET /api/auth/mewith the token to get user info
// 1. Get Google auth URL with return_to
const res = await fetch(
"https://ai-teammate.net/api/auth/google?return_to=" +
encodeURIComponent("https://myapp.com/auth/callback")
);
const { url } = await res.json();
window.location.href = url;
// 2. In your callback page
const token = new URLSearchParams(location.search).get("token");
const me = await fetch("https://ai-teammate.net/api/auth/me", {
headers: { Authorization: "Bearer " + token },
}).then(r => r.json());
// me: { name, email, avatar_url, ... }
Branding API (White-label)
Retrieve branding configuration by domain or slug. No authentication required. External apps can dynamically apply logo, colors, and app name at runtime.
GET /api/apps/branding?domain=fleet.mycompany.com
GET /api/apps/branding?slug=my-fleet-app
Response:
{
"name": "My Fleet Control",
"subtitle": "Smart Factory Ops",
"logo": "https://mycompany.com/logo.png",
"accent": "#1428A0",
"bg": "#0d1117",
"agent_id": "abc-123"
}
If no matching app is found, default values are returned. Use this to build white-label apps without source code changes — a single build serves multiple brands based on the domain.
Changelog
- v1.2 (2026-04-05): SSE Streaming Protocol, OAuth External App Integration, Branding API
- v1.1 (2026-03-24): Unified Bookmarks (
/bookmarks/end-user/), App-scoped API Keys, Dynamic CORS - v1.0 (2026-03-23): Initial release — Chat, Auth, Bookmarks, Webhooks, Skill blocks