How the Built-in API Server in Cherry Studio Works: Architecture and Implementation Guide

Cherry Studio's built-in API server is an Express-based HTTP API that runs inside the Electron main process, exposing OpenAI-compatible endpoints for model listing, chat completions, and Anthropic-style message streaming while authenticating requests against user-configured LLM providers.

The built-in API server in Cherry Studio enables external clients and the React renderer to interact with configured LLM providers through a unified REST interface. Running directly within the Electron main process, this lightweight server shares the Redux state and logging services with the desktop UI, ensuring provider configurations remain synchronized between the graphical interface and API consumers.

High-Level Architecture

The server follows a layered Express architecture defined in src/main/apiServer/app.ts. This bootstrap file initializes the application, configures global middleware, mounts route handlers, and starts the HTTP listener on a random available port.

Core Layers

  • Server bootstrap (src/main/apiServer/app.ts): Creates the Express instance, enables JSON parsing and CORS, adds request-ID headers, and mounts versioned routes under /v1/* alongside health endpoints and OpenAPI documentation.

  • Middleware stack (src/main/apiServer/middleware/):

    • authMiddleware (auth.ts): Validates the Authorization header against API keys stored for configured providers.
    • errorHandler (error.ts): Catches synchronous and asynchronous errors, formatting them into uniform JSON error objects with type, message, and code properties.
    • Timeout extension: extendMessagesTimeout raises request timeouts for long-running streaming calls.
  • Routing layer (src/main/apiServer/routes/): Dedicated routers handle models, chat, messages, MCP, and agents, each validating inputs with Zod schemas before delegating to services.

  • Service layer (src/main/apiServer/services/):

    • ModelsService (models.ts): Aggregates available models from all providers, handles deduplication, and applies pagination.
    • ChatCompletionService (chat-completion.ts): Builds OpenAI-compatible requests and manages streaming via Server-Sent Events (SSE).
    • MessagesService (messages.ts): Handles Anthropic endpoint communication with request validation and SSE streaming.
  • Provider utilities (src/main/apiServer/utils/index.ts): Shared helpers for reading the Redux store (getAvailableProviders), parsing model IDs in providerId:modelId format, and caching provider lists.

Request Flow Example

A POST /v1/chat/completions request flows through the following pipeline:

  1. Express receives the request and applies CORS and JSON parsing middleware.
  2. authMiddleware verifies the bearer token against stored provider configurations.
  3. The chatRoutes router validates the payload schema and calls chatCompletionService.processCompletion (or processStreamingCompletion when stream: true).
  4. The service builds a provider-specific request using the OpenAI SDK client and returns either a JSON response or SSE chunks.
  5. Any thrown errors bubble to errorHandler, which serializes them into standard JSON error responses.

Authentication and Security

The authMiddleware in src/main/apiServer/middleware/auth.ts guards all endpoints by validating the Authorization header against the API keys stored for each configured LLM provider in the Redux store. This ensures that external clients must present valid credentials matching the user's desktop application configuration before accessing model inference capabilities.

Core Endpoints and Services

Model Listing (GET /v1/models)

The models endpoint in src/main/apiServer/routes/models.ts delegates to ModelsService.getModels, which retrieves enabled providers from Redux via getAvailableProviders and transforms each model using transformModelToOpenAI in src/main/apiServer/utils/index.ts. The response follows the OpenAI list format with pagination support via limit and offset query parameters.

Key function: transformModelToOpenAI maps internal provider metadata to OpenAI-compatible model objects including id, owned_by, and provider-specific fields.

Chat Completions (POST /v1/chat/completions)

Located in src/main/apiServer/routes/chat.ts, this endpoint accepts OpenAI-compatible request bodies containing model, messages, and optional stream parameters.

Non-streaming flow: The route calls chatCompletionService.processCompletion, which instantiates an OpenAI SDK client via openaiService.getClient and returns the complete JSON response.

Streaming flow: When stream: true, the service calls processStreamingCompletion, which writes SSE chunks (data: lines) to the response using res.write() and flushes after each chunk to maintain real-time delivery.

Anthropic Messages (POST /v1/messages)

The messages router in src/main/apiServer/routes/messages.ts exposes Anthropic-style endpoints for compatibility with Claude-specific integrations. The MessagesService.handleStreaming method manages SSE streaming for these requests, writing event: and data: formatted chunks that match the Anthropic streaming contract while abstracting provider-specific implementation details.

Implementation Examples

Starting the Server

The Electron main process initializes the server in main.ts using a random port:

import { app as apiApp } from './src/main/apiServer/app';

const server = apiApp.listen(0, () => {
  const address = server.address() as any;
  logger.info('Cherry Studio API server listening', { port: address.port });
});

Port 0 instructs the operating system to assign a random free port, which the application logs for client consumption.

Listing Available Models

curl -H "Authorization: Bearer <API_KEY>" \
     "http://localhost:PORT/v1/models?providerType=anthropic&limit=10&offset=0"

Response structure:

{
  "object": "list",
  "data": [
    {
      "id": "anthropic:claude-3-5-sonnet-20241022",
      "object": "model",
      "name": "Claude 3.5 Sonnet",
      "created": 1700000000,
      "owned_by": "anthropic",
      "provider": "anthropic",
      "provider_name": "Anthropic",
      "provider_type": "anthropic",
      "provider_model_id": "claude-3-5-sonnet-20241022"
    }
  ],
  "total": 12,
  "offset": 0,
  "limit": 10
}

Non-Streaming Chat Completion

curl -X POST -H "Content-Type: application/json" \
     -H "Authorization: Bearer <API_KEY>" \
     -d '{
           "model":"openai:gpt-4o-mini",
           "messages":[{"role":"user","content":"Explain quantum tunnelling"}],
           "max_tokens":256
         }' \
     http://localhost:PORT/v1/chat/completions

Streaming Anthropic Messages

curl -N -H "Authorization: Bearer <API_KEY>" \
     -H "Accept: text/event-stream" \
     -d '{
           "model":"anthropic:claude-3-5-sonnet-20241022",
           "messages":[{"role":"user","content":"Write a haiku about sunrise"}],
           "stream":true
         }' \
     http://localhost:PORT/v1/messages

The -N flag disables buffering to display SSE chunks in real-time as MessagesService.handleStreaming writes them to the response.

Key Source Files

The following files implement the core API server functionality in the cherryhq/cherry-studio repository:

Summary

Cherry Studio's built-in API server provides a production-ready HTTP interface for LLM interactions:

  • Runs as an Express application inside the Electron main process, sharing Redux state with the desktop UI.
  • Exposes OpenAI-compatible endpoints (/v1/models, /v1/chat/completions) and Anthropic-compatible endpoints (/v1/messages) with uniform authentication.
  • Validates requests through authMiddleware against stored provider API keys and uses errorHandler for consistent error formatting.
  • Delegates to specialized service classes (ChatCompletionService, MessagesService) that handle provider-specific SDK integration and SSE streaming.
  • Supports both synchronous JSON responses and real-time streaming via Server-Sent Events for compatible clients.

External tools, scripts, or integrations can interact with Cherry Studio exactly as they would with hosted OpenAI or Anthropic services, while the desktop application maintains full control over provider configuration and API key management.

Frequently Asked Questions

What port does the Cherry Studio API server use?

The server binds to port 0 (random available port) when started from the Electron main process. The actual port number is logged at startup and can be retrieved programmatically from the server address object, allowing external clients to discover the endpoint dynamically.

How does the API server authenticate incoming requests?

The authMiddleware in src/main/apiServer/middleware/auth.ts validates the Authorization header bearer token against the API keys stored for each configured LLM provider in the Redux store. Requests must present credentials matching one of the user's configured providers to proceed.

Can I use the Cherry Studio API server with OpenAI-compatible SDKs?

Yes. The server implements the standard OpenAI REST API contract for model listing and chat completions, including SSE streaming support. You can point OpenAI client libraries to http://localhost:PORT/v1 using your Cherry Studio API key to interact with configured providers transparently.

How does streaming work for chat completions?

When stream: true is specified in the request body, ChatCompletionService.processStreamingCompletion writes Server-Sent Events (SSE) formatted chunks (data: JSON objects) to the HTTP response, flushing each chunk immediately. This matches the OpenAI streaming contract and allows real-time token delivery to clients.

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 →