syaOS syaOS / Docs
GitHub Launch

API Architecture

syaOS uses Vercel Edge Functions for its backend API, with AI providers, Redis caching, and real-time features.

Architecture Overview

graph TB
    subgraph Client["Client Layer"]
        UI[React UI]
        Hooks[Custom Hooks]
    end
    
    subgraph Edge["Vercel Edge Functions"]
        ChatAPI[chat]
        SongAPI[song]
        SpeechAPI[speech]
        RoomsAPI[chat-rooms]
        IEAPI[ie-generate]
        AppletAPI[applet-ai]
    end
    
    subgraph AI["AI Providers"]
        OpenAI[OpenAI]
        Anthropic[Anthropic]
        Google[Google]
    end
    
    subgraph Storage["Storage"]
        Redis[(Redis)]
        KV[(Vercel KV)]
    end
    
    subgraph Realtime["Real-time"]
        Pusher[Pusher]
    end
    
    UI --> Hooks
    Hooks --> Edge
    Edge --> AI
    Edge --> Storage
    Edge --> Realtime
    RoomsAPI --> Pusher

API Directory Structure

api/
├── _utils/                    # Shared utilities
│   ├── aiModels.ts           # AI provider abstraction
│   ├── aiPrompts.ts          # System prompts
│   ├── auth-validate.ts      # Token validation
│   ├── cors.ts               # CORS handling
│   ├── rate-limit.ts         # Rate limiting
│   ├── song-service.ts       # Song data service
│   └── sse.ts                # Server-Sent Events
├── chat-rooms/               # Multi-file module
│   ├── index.ts              # HTTP handlers
│   ├── _messages.ts          # Message handlers
│   ├── _presence.ts          # Presence tracking
│   ├── _pusher.ts            # Pusher integration
│   └── _redis.ts             # Redis client
├── song/                     # Multi-file module
│   ├── index.ts              # List/batch operations
│   ├── [id].ts               # Single song CRUD
│   ├── _lyrics.ts            # Lyrics parsing
│   └── _furigana.ts          # Japanese furigana
└── [endpoint].ts             # Individual endpoints

API Endpoint Inventory

Core APIs

EndpointMethodsPurpose
/api/chatPOSTAI chat with streaming and tool calling
/api/speechPOSTText-to-speech synthesis
/api/audio-transcribePOSTSpeech-to-text (Whisper)

Media APIs

EndpointMethodsPurpose
/api/songGET, POST, DELETESong list and batch operations
/api/song/[id]GET, POST, DELETESingle song CRUD + lyrics
/api/youtube-searchPOSTYouTube video search
/api/parse-titleGETYouTube title parsing
/api/coverGETAlbum art fetching

AI Generation APIs

EndpointMethodsPurpose
/api/ie-generatePOSTInternet Explorer time-travel
/api/applet-aiPOSTApplet AI assistant

Communication APIs

EndpointMethodsPurpose
/api/chat-roomsGET, POST, DELETEReal-time chat rooms
/api/share-appletGET, POST, PATCH, DELETEApplet sharing/store

Utility APIs

EndpointMethodsPurpose
/api/iframe-checkGETCheck/proxy iframe embedding
/api/link-previewGETOpenGraph metadata extraction
/api/adminVariousAdmin operations

AI Provider Abstraction

Supported Models

ProviderModelsUse Cases
OpenAIgpt-5, gpt-5.1, gpt-5-mini, gpt-4o, gpt-4.1Default chat, code generation
Anthropicclaude-4.5, claude-4, claude-3.7, claude-3.5Complex reasoning
Googlegemini-2.5-pro, gemini-2.5-flash, gemini-3-proImage generation

Model Selection

// _api/_utils/aiModels.ts
export const getModelInstance = (model: SupportedModel): LanguageModelV2 => {
  switch (model) {
    case "gpt-5":
      return openai("gpt-5");
    case "gpt-5.1":
      return openai("gpt-5.1");
    case "claude-4.5":
      return anthropic("claude-sonnet-4-5");
    case "gemini-2.5-pro":
      return google("gemini-2.5-pro");
    // ... more models
  }
};

Chat API

Streaming Architecture

sequenceDiagram
    participant Client
    participant API as /api/chat
    participant AI as AI Provider
    participant Tools as Tool Handlers
    
    Client->>API: POST (messages, model)
    API->>AI: streamText()
    
    loop Stream Response
        AI-->>API: Token/Tool Call
        
        alt Tool Call
            API->>Tools: Execute tool
            Tools-->>API: Tool result
            API->>AI: Continue with result
        else Text Token
            API-->>Client: SSE chunk
        end
    end
    
    API-->>Client: Stream complete

Tool Calling System

The chat API provides tools for system control:

tools: {
  launchApp: {
    description: "Launch syaOS application",
    parameters: z.object({
      appId: z.enum(["finder", "textedit", "ipod", ...]),
      initialData: z.unknown().optional(),
    }),
  },
  
  ipodControl: {
    description: "Control iPod playback",
    parameters: z.object({
      action: z.enum(["toggle", "play", "pause", "next", "previous", ...]),
      title: z.string().optional(),
      artist: z.string().optional(),
    }),
  },
  
  generateHtml: {
    description: "Generate HTML applet",
    parameters: z.object({
      title: z.string(),
      icon: z.string(),
      code: z.string(),
    }),
  },
  
  // ... more tools
}

System Prompts

// _api/_utils/aiPrompts.ts
export const CORE_PRIORITY_INSTRUCTIONS = `
  You are Ryo, an AI assistant in syaOS...
`;

export const RYO_PERSONA_INSTRUCTIONS = `
  Ryo's personality and background...
`;

export const CODE_GENERATION_INSTRUCTIONS = `
  HTML applet generation rules...
`;

export const TOOL_USAGE_INSTRUCTIONS = `
  VFS and tool usage patterns...
`;

Song API

Split Storage Architecture

flowchart TB
    subgraph "Request"
        REQ[API Request]
    end
    
    subgraph "Redis Storage"
        META[(song:meta:*
Lightweight)] CONTENT[(song:content:*
Heavy Data)] SET[(song:all
ID Set)] end REQ --> |List/Search| META REQ --> |Full Song| META META --> |UUID Lookup| CONTENT REQ --> |All IDs| SET

Endpoints

RouteActionDescription
GET /api/songListSongs with filters
POST /api/songCreate/ImportNew songs or bulk import
GET /api/song/[id]ReadSong with lyrics, translations
POST /api/song/[id]?action=fetch-lyricsFetchGet lyrics from Kugou
POST /api/song/[id]?action=translate-streamTranslateAI translation (SSE)
POST /api/song/[id]?action=furigana-streamFuriganaAI furigana (SSE)
DELETE /api/song/[id]DeleteRemove song

Speech APIs

Text-to-Speech

// api/speech.ts
const providers = {
  openai: {
    models: ["tts-1", "tts-1-hd"],
    voices: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
  },
  elevenlabs: {
    models: ["eleven_multilingual_v2", "eleven_turbo_v2"],
    voices: ["custom voice IDs"],
  },
};

// Dual provider support
if (model === "elevenlabs") {
  return generateElevenLabsSpeech(text, voiceId, modelId);
} else {
  return generateSpeech({
    model: openai.speech("tts-1"),
    text,
    voice,
  });
}

Audio Transcription

// api/audio-transcribe.ts
const transcription = await openai.audio.transcriptions.create({
  file: audioFile,
  model: "whisper-1",
});

Rate Limiting

Counter-Based Limiting

// _api/_utils/rate-limit.ts
export async function checkCounterLimit({
  key,
  windowSeconds,
  limit,
}: CounterLimitArgs): Promise<CounterLimitResult> {
  // Atomic increment
  const newCount = await redis.incr(key);
  
  // Set expiry on first request
  if (newCount === 1) {
    await redis.expire(key, windowSeconds);
  }
  
  const ttl = await redis.ttl(key);
  
  if (newCount > limit) {
    return { 
      allowed: false, 
      count: newCount, 
      remaining: 0,
      resetSeconds: ttl,
    };
  }
  
  return { 
    allowed: true, 
    remaining: limit - newCount,
  };
}

Rate Limit Configurations

EndpointBurst LimitDaily/BudgetWindow
Chat AI (auth)25per 5 hoursSliding
Chat AI (anon)3per 5 hoursSliding
Speech TTS10/min50/dayFixed
IE Generate3/min10/5hrFixed
Applet AI (auth)50per hourFixed
Applet AI (anon)15per hourFixed
Transcribe10/min50/dayFixed

Authentication

Token Validation

// _api/_utils/auth-validate.ts
export async function validateAuthToken(
  redis: RedisLike,
  username: string,
  authToken: string,
  options: { allowExpired?: boolean; refreshOnGrace?: boolean } = {}
): Promise<AuthValidationResult> {
  // 1. Check active token
  const userKey = getUserTokenKey(username, authToken);
  const exists = await redis.exists(userKey);
  
  if (exists) {
    // Refresh TTL on use
    await redis.expire(userKey, USER_TTL_SECONDS);
    return { valid: true, expired: false };
  }
  
  // 2. Check grace period for expired tokens
  if (options.allowExpired) {
    const lastTokenKey = getLastTokenKey(username);
    const lastTokenData = await redis.get(lastTokenKey);
    // ... grace period logic
  }
  
  return { valid: false };
}

Auth Headers

HeaderValuePurpose
AuthorizationBearer <token>Authentication token
X-UsernameUsernameUser identification

Token Configuration

SettingValue
Token TTL90 days
Grace period30 days
Admin bypassUser "ryo"

CORS Handling

// _api/_utils/cors.ts
export function isAllowedOrigin(origin: string | null): boolean {
  if (!origin) return false;
  
  // Always allow Tailscale origins
  if (isTailscaleOrigin(origin)) return true;
  
  const env = getRuntimeEnv();
  
  if (env === "production") {
    return origin === PROD_ALLOWED_ORIGIN;
  }
  
  if (env === "preview") {
    return isVercelPreviewOrigin(origin);
  }
  
  // Development
  return isLocalhostOrigin(origin);
}

Response Patterns

JSON Response Helper

const jsonResponse = (data: unknown, status = 200, headers = {}) =>
  new Response(JSON.stringify(data), {
    status,
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": effectiveOrigin,
      ...headers,
    },
  });

Error Response Pattern

const errorResponse = (message: string, status = 400) => {
  logInfo(requestId, `Response: ${status} - ${message}`);
  return jsonResponse({ error: message }, status);
};

SSE Streaming Response

const stream = new ReadableStream({
  async start(controller) {
    for await (const chunk of aiStream) {
      controller.enqueue(`data: ${JSON.stringify(chunk)}\n\n`);
    }
    controller.enqueue("data: [DONE]\n\n");
    controller.close();
  },
});

return new Response(stream, {
  headers: {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
  },
});

Edge Runtime Configuration

All APIs use Vercel Edge Runtime for low latency:

export const config = { runtime: "edge" };
export const runtime = "edge";
export const maxDuration = 60; // or 80 for AI endpoints

Redis Key Patterns

PrefixPurposeExample
rl:ai:AI rate limitingrl:ai:chat:user123
song:meta:Song metadatasong:meta:abc123
song:content:Song contentsong:content:abc123
ie:cache:IE generation cacheie:cache:hash
applet:share:Shared appletsapplet:share:xyz
chat:token:user:Auth tokenschat:token:user:alice:tok
chat:room:Chat room datachat:room:general

Related Documentation

  • Chat API - Detailed chat API documentation