ryOS ryOS / Docs
GitHub Launch

ryOS API Design Guide

This document outlines the current patterns and conventions used in the ryOS API.

Architecture Overview

The API is implemented as Node.js route handlers under api/. The default implementation pattern is the shared apiHandler wrapper, which centralizes:

  • CORS preflight and response headers
  • origin allowlist checks
  • method allowlists
  • optional JSON body handling
  • optional/required auth resolution
  • logger setup and consistent fallback error handling

Auth boundaries are unified through request-auth.ts, which enforces paired auth headers.

File Structure

api/
├── _utils/
│   ├── api-handler.ts      # Primary route wrapper
│   ├── request-auth.ts     # Unified auth extraction + token validation
│   ├── redis.ts            # Redis client factory (Upstash REST + standard Redis backends)
│   ├── storage.ts          # Switchable object storage (Vercel Blob / S3)
│   ├── contacts.ts         # Contacts state read/write helpers
│   ├── _cors.ts            # Origin policy + CORS helpers
│   ├── _rate-limit.ts      # Counter-based rate limit utilities
│   ├── _validation.ts      # Input/profanity/escaping helpers
│   ├── _ssrf.ts            # Safe URL fetch + SSRF protections
│   ├── _sse.ts             # SSE stream helpers
│   ├── _logging.ts         # Request-scoped logger
│   ├── _aiModels.ts        # AI model registry and factory
│   ├── _aiPrompts.ts       # Shared AI prompt templates
│   └── constants.ts        # Shared constants
├── auth/                   # Auth/session endpoints
├── chat/                   # Main AI chat endpoint + tool definitions
├── rooms/                  # Chat rooms/messages/presence
├── listen/                 # Listen Together session APIs
├── songs/                  # Song library + lyric processing
├── ai/                     # Memory processing + room AI reply
├── sync/                   # Cloud backup + auto-sync endpoints
├── telegram/               # Telegram account linking
└── ...                     # Other feature routes (stocks, speech, etc.)

Naming Conventions

  • _helpers/ for domain-specific internal helpers
  • _utils/ for globally shared API utilities
  • _*.ts files for internal/private modules
  • index.ts for collection routes
  • [id].ts and nested dynamic route folders for path params

Common Patterns

1. Runtime Declaration

export const runtime = "nodejs";
export const maxDuration = 30; // endpoint-dependent

2. apiHandler Wrapper (Primary Pattern)

import { apiHandler } from "../_utils/api-handler.js";

export default apiHandler(
  {
    methods: ["GET", "POST"],
    auth: "optional",         // "none" | "optional" | "required"
    allowExpiredAuth: false,  // optional
    parseJsonBody: true,      // optional
    contentType: "application/json", // optional, null disables default header
  },
  async ({ req, res, redis, logger, startTime, origin, user, body }) => {
    // business logic
    logger.response(200, Date.now() - startTime);
    res.status(200).json({ ok: true });
  }
);
apiHandler provides:
  • OPTIONS handling (204) with endpoint-specific method list
  • consistent 403 for disallowed origins
  • consistent 405 for unsupported methods
  • Redis client injection via createRedis()
  • auth resolution via resolveRequestAuth()
  • fallback 500 JSON error guard

3. Endpoint Function Signature

When using apiHandler, handlers receive:

  • req, res
  • redis
  • logger, startTime
  • origin
  • user (null or resolved auth user)
  • body (null or parsed JSON when parseJsonBody: true)

4. Auth Boundaries via request-auth

resolveRequestAuth() enforces a shared policy:
  • required auth endpoints need both headers:
    • Authorization: Bearer <token>
    • X-Username: <username>
  • partial credentials return 400
  • invalid token/username pairs return 401
  • optional-auth endpoints allow anonymous requests, but validate credentials if provided

For non-apiHandler routes (legacy/multipart), call resolveRequestAuth() directly to keep behavior aligned.

5. Rate Limiting

Use _utils/_rate-limit.ts primitives:

import * as RateLimit from "../_utils/_rate-limit.js";
import { getClientIp } from "../_utils/_rate-limit.js";

const ip = getClientIp(req);
const key = RateLimit.makeKey(["rl", "feature", "burst", "ip", ip]);
const result = await RateLimit.checkCounterLimit({
  key,
  windowSeconds: 60,
  limit: 30,
});

if (!result.allowed) {
  res.setHeader("Retry-After", String(result.resetSeconds));
  return res.status(429).json({
    error: "rate_limit_exceeded",
    limit: result.limit,
    retryAfter: result.resetSeconds,
  });
}

6. Response & Error Handling

The preferred API shape is JSON for standard requests and SSE for streaming endpoints.

  • success: explicit payloads ({ success: true }, { data: ... }, etc.)
  • client errors: 400/401/403/404/405/429 with JSON { error: "..." }
  • server errors: 500 with JSON { error: "..." }

For SSE endpoints, set stream headers explicitly and send structured events (start, line, complete, error, etc.).

7. Manual Handlers (When Needed)

Some endpoints intentionally keep explicit handlers (for example multipart uploads such as /api/audio-transcribe).

When writing a manual handler, still mirror shared behavior:

  • getEffectiveOrigin() + isAllowedOrigin() + setCorsHeaders()
  • method checks and OPTIONS handling
  • initLogger() + status timing logs
  • resolveRequestAuth() for authenticated routes

8. Logging

Use request-scoped logger utilities from _utils/_logging.ts:

import { initLogger } from "../_utils/_logging.js";

const { logger } = initLogger();
logger.request(req.method || "GET", req.url || "/api/example");
logger.info("Processing request", { user: "alice" });
logger.response(200, Date.now() - startTime);

9. Utility Modules

Constants

Use shared constants from _utils/constants.ts where possible instead of hardcoding:

  • REDIS_PREFIXES
  • TTL
  • RATE_LIMIT_TIERS
  • PASSWORD
  • VALIDATION
  • TOKEN

Feature-specific constants should stay in feature helper modules (for example rooms/_helpers/_constants.ts, listen/_helpers/_constants.ts).

Best Practices

  1. Prefer apiHandler for new JSON endpoints.
  1. Keep auth semantics consistent through request-auth.
  2. Apply rate limits to public and expensive routes.
  3. Validate all user-provided input before use.
  4. Keep response shapes stable and explicit.
  5. Use SSRF-safe fetch helpers for untrusted URLs.
  6. Add structured logs for request/response and key branch decisions.
  7. Keep endpoint docs updated whenever request/response contracts change.

Error Response Format

Most endpoints use:

{
  "error": "Human-readable error message"
}

Some endpoints include extra fields (for example rate-limit metadata, validation details, or endpoint-specific diagnostics). Keep these additions additive and backward-compatible.