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

  1. Always authenticate - Include Firebase token in Authorization header
  2. Handle errors - Check response status and handle errors gracefully
  3. Use streaming - For better UX, use /api/ai/stream for long responses
  4. Validate input - Client-side validation before sending
  5. Show loading states - Display typing indicator during requests
  6. 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