Complete reference for AI-related API endpoints in ShipSafe AI-SaaS.
Overview
ShipSafe AI-SaaS provides secure API routes for AI functionality:
/api/ai/chat- Standard chat completion endpoint/api/ai/stream- Streaming chat completion endpoint (SSE)/api/chat- Alternative chat endpoint (alias)
All routes require:
- Authentication (Firebase Auth token)
- Active subscription
- Input validation (Zod)
- Content moderation
/api/ai/chat
Standard chat completion endpoint. Returns complete response after generation.
Request
POST /api/ai/chat
Content-Type: application/json
Authorization: Bearer <firebase-token>
{
"message": "What is TypeScript?",
"messages": [ // Optional: conversation history
{ "role": "user", "content": "Hello" },
{ "role": "assistant", "content": "Hi there!" }
],
"systemPrompt": "...", // Optional: custom system prompt
"temperature": 0.7, // Optional: 0-2, default 0.7
"maxTokens": 2000, // Optional: default 2000
"provider": "openai" // Optional: "openai" | "anthropic"
}
Response
{
"success": true,
"data": {
"response": "TypeScript is a typed superset of JavaScript...",
"provider": "openai",
"model": "gpt-4-turbo-preview",
"usage": {
"promptTokens": 10,
"completionTokens": 50,
"totalTokens": 60
}
}
}
Error Responses
403 Forbidden - No active subscription:
{
"error": "Subscription required",
"message": "Please upgrade your plan to access AI features."
}
400 Bad Request - Invalid input:
{
"error": "Invalid prompt",
"details": "Prompt too long. Maximum length is 50000 characters."
}
400 Bad Request - Content moderation failed:
{
"error": "Content moderation failed",
"reason": "Content flagged as inappropriate"
}
Example Usage
const response = await fetch("/api/ai/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
message: "Explain React hooks",
temperature: 0.7,
}),
});
const data = await response.json();
console.log(data.data.response);
/api/ai/stream
Streaming chat completion endpoint using Server-Sent Events (SSE). Returns response chunks in real-time.
Request
POST /api/ai/stream
Content-Type: application/json
Authorization: Bearer <firebase-token>
{
"message": "Explain quantum computing",
"messages": [...], // Optional: conversation history
"systemPrompt": "...", // Optional
"temperature": 0.7,
"maxTokens": 2000,
"provider": "openai"
}
Response
Server-Sent Events stream:
data: {"content":"Quantum","done":false}
data: {"content":" computing","done":false}
data: {"content":" is...","done":false}
data: {"content":"","done":true}
Stream Chunk Format
interface StreamChunk {
content: string; // Text chunk
done: boolean; // true when stream is complete
provider?: "openai" | "anthropic";
model?: string;
}
Error Responses
Errors are sent as SSE events:
data: {"error":"Subscription required","done":true}
Example Usage
const response = await fetch("/api/ai/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
message: "Explain quantum computing",
}),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
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));
if (!data.done) {
appendToChat(data.content);
}
}
}
}
/api/chat
Alternative chat endpoint (alias for /api/ai/chat). Same functionality and request/response format.
Note: This endpoint exists for compatibility but /api/ai/chat is preferred.
Security Features
All AI API routes include:
1. Authentication
// Automatically handled by requireAuth()
const user = await requireAuth(req);
2. Subscription Check
const hasAccess = await hasActiveSubscription(user.uid);
if (!hasAccess) {
return NextResponse.json(
{ error: "Subscription required" },
{ status: 403 }
);
}
3. Input Validation
const chatRequestSchema = z.object({
message: z.string().min(1).max(10000),
messages: z.array(z.object({
role: z.enum(["user", "assistant", "system"]),
content: z.string(),
})).optional(),
// ... more validation
});
const parsed = chatRequestSchema.parse(body);
4. Content Moderation
const moderation = await moderatePrompt(userMessage, {
useOpenAIModeration: true,
checkInjection: true,
});
if (!moderation.allowed) {
return NextResponse.json(
{ error: "Content moderation failed" },
{ status: 400 }
);
}
5. Prompt Validation
const validation = validatePrompt(messageHistory);
if (!validation.valid) {
return NextResponse.json(
{ error: "Invalid prompt", details: validation.error },
{ status: 400 }
);
}
6. Tiered Prompts
const tier = await getSubscriptionTier(user.uid);
const messages = buildMessageHistoryWithTier(messageHistory, { tier });
Rate Limiting
AI routes are protected by ShipSafe's rate limiting middleware:
- Per-IP limits - Prevents abuse
- Per-user limits - Subscription-based limits
- Per-endpoint limits - Different limits for chat vs stream
Error Handling
Client-Side
try {
const response = await fetch("/api/ai/chat", { ... });
if (response.status === 403) {
// Subscription required
const data = await response.json();
alert(data.message);
} else if (response.status === 400) {
// Invalid input or moderation failed
const data = await response.json();
console.error(data.error, data.details);
} else if (!response.ok) {
// Other errors
throw new Error("Request failed");
}
const data = await response.json();
// Handle success
} catch (error) {
console.error("Chat error:", error);
}
Server-Side
All routes use try/catch with proper error responses:
try {
// ... request processing
} catch (error) {
console.error("API error:", error);
return NextResponse.json(
{ error: "Server error" },
{ status: 500 }
);
}
Best Practices
- Always authenticate - Include Firebase token in Authorization header
- Handle errors - Check response status and handle errors gracefully
- Use streaming - For better UX, use
/api/ai/streamfor long responses - Validate input - Client-side validation before sending
- Show loading states - Display typing indicator during requests
- Limit message history - Don't send entire conversation history for very long conversations
Integration Examples
With ChatInterface Component
const handleSendMessage = async (message: string) => {
setIsLoading(true);
try {
const token = await user.getIdToken();
const response = await fetch("/api/ai/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
message,
messages: conversationHistory,
}),
});
const data = await response.json();
setMessages(prev => [...prev, {
role: "assistant",
content: data.data.response,
}]);
} catch (error) {
console.error("Error:", error);
} finally {
setIsLoading(false);
}
};
With Streaming
const handleStreamMessage = async (message: string) => {
setIsTyping(true);
setStreamingContent("");
const token = await user.getIdToken();
const response = await fetch("/api/ai/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify({ message }),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// Parse and append chunks
appendChunk(chunk);
}
setIsTyping(false);
};
Next Steps
- Learn about AI Client for direct client usage
- Explore Streaming for real-time responses
- Check out Subscription Access for access control
- Review Content Moderation for safety features