How to Integrate 9router API Handlers with a Frontend Application

Integrate your frontend with 9router by calling the Next.js API routes (/api/v1/...) using standard fetch requests with OpenAI-compatible JSON payloads, where the backend handles provider authentication, request translation, and streaming responses automatically.

The decolua/9router repository provides a Next.js backend that proxies requests to multiple AI providers through a unified OpenAI-compatible interface. When building a frontend application—whether React, Vue, or vanilla JavaScript—you interact with these endpoints exactly as you would with the official OpenAI API, without managing provider credentials or provider-specific SDKs on the client side.

Understanding the 9router Architecture

9router exposes HTTP endpoints under src/app/api/v1/... that act as thin wrappers around core handler functions. These handlers, located in open-sse/handlers/, normalize incoming requests, manage provider-specific authentication, and return either JSON or Server-Sent Events (SSE) streams.

API Route Structure

Each endpoint in src/app/api/v1/ corresponds to a specific AI capability. For example, src/app/api/v1/chat/completions/route.js handles chat completions by forwarding requests to handleChatCore. The routes implement comprehensive CORS handling via OPTIONS method handlers, allowing requests from any origin without additional server configuration.

Core Handler Logic

The actual business logic resides in the open-sse directory. Files like open-sse/handlers/chatCore.js manage token refresh, provider selection, and response formatting. When a frontend sends a request to /api/v1/chat/completions, the route handler invokes the core logic, which then communicates with the configured provider (OpenAI, Claude, etc.) through specialized executors in open-sse/executors/.

Sending Requests from the Frontend

Because 9router accepts standard OpenAI-compatible payloads, you can use the native fetch API without installing provider-specific SDKs.

Standard JSON Requests (Chat Completions)

For non-streaming responses, send a POST request to /api/v1/chat/completions with a JSON body containing the model and messages array:

async function callChatCompletion(messages) {
  const response = await fetch('/api/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'gpt-4o-mini',   // Must match a provider-configured model
      messages,
      stream: false           // Return JSON response
    })
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(`API error ${response.status}: ${err.error}`);
  }

  const data = await response.json();
  return data;               // { choices: [{ message: { content: '…' } }], … }
}

The route src/app/api/v1/chat/completions/route.js processes this request and returns a standard OpenAI-compatible completion object.

Handling Server-Sent Events (SSE) Streams

When you set stream: true, 9router returns an SSE stream instead of JSON. Consume this using the Fetch Streams API to process tokens as they arrive:

async function streamChatCompletion(messages, onDelta) {
  const response = await fetch('/api/v1/chat/completions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages,
      stream: true               // Enable SSE streaming
    })
  });

  if (!response.body) throw new Error('ReadableStream not supported');

  const reader = response.body.getReader();
  const decoder = new TextDecoder('utf-8');
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    // SSE events are separated by double newlines
    const events = buffer.split('\n\n');
    buffer = events.pop(); // Keep incomplete chunk for next iteration

    for (const ev of events) {
      if (ev.startsWith('data:')) {
        const payload = ev.slice(5).trim();
        if (payload === '[DONE]') return; // Stream termination signal
        const data = JSON.parse(payload);
        const delta = data?.choices?.[0]?.delta?.content;
        if (delta) onDelta(delta);
      }
    }
  }
}

The core streaming logic in open-sse/handlers/chatCore.js ensures the SSE format matches OpenAI's specification exactly, allowing you to use standard parsing logic.

Image Generation Endpoints

For image generation, post to /api/v1/images/generations with parameters matching the DALL-E API structure:

async function generateImage(prompt) {
  const response = await fetch('/api/v1/images/generations', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'dall-e-3',
      prompt,
      n: 1,
      size: '1024x1024'
    })
  });

  const data = await response.json();
  // data.data[0].url contains the generated image URL
  return data.data[0].url;
}

This endpoint, defined in src/app/api/v1/images/generations/route.js, forwards to handleImageGenerationCore in open-sse/handlers/imageGenerationCore.js.

Web Scraping Capabilities

9router also provides a generic fetch utility for web scraping accessible via /api/v1/web/fetch:

async function fetchWebPage(url) {
  const response = await fetch('/api/v1/web/fetch', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ url })
  });

  const { content } = await response.json();
  return content; // Raw HTML or extracted text depending on configuration
}

The handler in open-sse/handlers/fetch/index.js processes this request, allowing your frontend to retrieve external content without violating CORS policies.

Key Source Files and Implementation Details

Understanding these specific files helps debug integration issues and customize behavior:

Integration Checklist

Ensure your frontend integration follows these verified patterns:

  • Base URL – All routes mount under /api/v1/ relative to your Next.js deployment
  • CORS Configuration – Routes automatically respond with Access-Control-Allow-Origin: * via built-in OPTIONS handlers
  • Request Schema – Use OpenAI-compatible JSON structures (model, messages, prompt, stream boolean)
  • Streaming Setup – Set "stream": true and implement ReadableStream processing for real-time responses
  • Error Handling – Expect { error: <message> } JSON for HTTP errors; streaming errors terminate with [DONE] signal
  • Security – 9router stores all provider API keys internally; frontend requests never transmit authentication secrets

Summary

  • 9router exposes OpenAI-compatible endpoints under /api/v1/ that proxy to multiple AI providers through Next.js API routes.
  • Frontend applications send standard fetch requests to these routes without managing provider credentials or SDKs.
  • Support both JSON and SSE streaming responses by toggling the stream parameter and using the Fetch Streams API.
  • Core logic in open-sse/handlers/ normalizes requests across providers, handles authentication, and manages error translation.
  • All endpoints include automatic CORS handling, making them safe to call from any frontend origin.

Frequently Asked Questions

What request format does 9router expect from the frontend?

9router expects OpenAI-compatible JSON payloads. For chat completions, include model, messages, and optional stream parameters. For images, provide prompt, model, n, and size. The schema matches the official OpenAI API specification exactly, allowing drop-in replacement of endpoint URLs.

How does 9router handle CORS for cross-origin requests?

Each API route in src/app/api/v1/ implements an OPTIONS method handler that returns Access-Control-Allow-Origin: * headers. This allows browser-based frontend applications hosted on different domains to call the 9router backend without triggering CORS errors or requiring additional proxy configuration.

Can I use the official OpenAI SDK with 9router endpoints?

Yes, you can configure the OpenAI JavaScript SDK to point to your 9router base URL instead of api.openai.com. Initialize the client with baseURL: 'https://your-domain.com/api/v1' and remove the default authentication header (or set it to a dummy value), as 9router manages provider credentials server-side in open-sse/config/.

Where are the provider credentials stored in a 9router integration?

Provider API keys and authentication tokens are stored server-side only in the 9router configuration files (typically in open-sse/config/ or environment variables accessed by the core handlers). The frontend never handles these secrets, making the architecture secure for public-facing applications.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →