Skip to content

Unified API Interface

Doc Version: 2.1.0
Last Updated: 2026-09-13
Main change: v3.1 dual entry surfaces (/v1 OpenAI-native + /api/v1 console) and auth conventions
Git Commit: 61384b4a
Author: Lincoln

JAiRouter provides a unified API interface compatible with OpenAI format, supporting multiple AI model services. The OpenAI-compatible surface lives under /v1; TTS / STT / image endpoints are exposed only on the console surface /api/v1/** (see the table below).

Two entry surfaces (since v3.1)

SurfacePathResponse shapeIntended use
OpenAI-native/v1/**Non-streaming: raw JSON ({id, object:"chat.completion", choices, usage}); streaming: native SSE (data: {...} separated by \n\n)Point OpenAI SDK / LangChain / LlamaIndex at base_url=http://<host>:8080/v1
Console/api/v1/**Wrapped in RouterResponse ({success, message, data})Web console / AI playground internal calls; not advertised as OpenAI-compatible

Authentication

  • Gateway credential: X-API-Key: <API key created in the console>, or Jairouter_Token: <JWT>.
  • The Authorization header is forwarded to the downstream AI service; instance-level headers take precedence — so the downstream key can live only on the instance (headers: {Authorization: "Bearer <downstream-key>"}) with no client-side duplication.
  • Service permission: the API key must carry the matching service role (CHAT / EMBEDDING / RERANK, or ADMIN).
  • Missing credentials → 401; insufficient permission → 403; quota exceeded → 429 (with Retry-After and X-Quota-*).

Changed in v3.1: /v1/** used to be forwarded to /api/v1/** by a path-rewrite filter that lost the request body (always 400 Failed to read HTTP message). It is now served natively by OpenAiNativeController, so /v1 genuinely works as an OpenAI SDK base_url.

Chat Completion Interface

POST /v1/chat/completions

Handles chat completion requests, supporting both streaming and non-streaming responses.

Request Parameters

{
  "model": "gpt-3.5-turbo",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant."
    },
    {
      "role": "user", 
      "content": "Hello!"
    }
  ],
  "stream": false,
  "max_tokens": 1000,
  "temperature": 0.7,
  "top_p": 1.0,
  "top_k": 50,
  "frequency_penalty": 0.0,
  "presence_penalty": 0.0,
  "stop": null,
  "user": "user-123"
}

Parameter Description

ParameterTypeRequiredDescription
modelstringYesThe name of the model to use
messagesarrayYesList of conversation messages
streambooleanNoWhether to enable streaming response, default is false
max_tokensintegerNoMaximum number of tokens to generate
temperaturenumberNoSampling temperature between 0-2, default is 1
top_pnumberNoNucleus sampling parameter between 0-1, default is 1
top_kintegerNoTop-K sampling parameter
frequency_penaltynumberNoFrequency penalty between -2 to 2, default is 0
presence_penaltynumberNoPresence penalty between -2 to 2, default is 0
stopstring/arrayNoStop sequences
userstringNoUser identifier

Message Format

{
  "role": "user|assistant|system",
  "content": "Message content",
  "name": "Optional sender name"
}

Response Format

Non-streaming Response:

{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-3.5-turbo",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello! I am an AI assistant, happy to serve you."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 20,
    "completion_tokens": 15,
    "total_tokens": 35
  },
  "system_fingerprint": "fp_44709d6fcb"
}

Streaming Response:

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Text Embedding Interface

POST /v1/embeddings

Converts text into vector representations for semantic search, similarity calculations, and other tasks.

Request Parameters

{
  "model": "text-embedding-ada-002",
  "input": "Text content to embed",
  "encoding_format": "float",
  "dimensions": 1536,
  "user": "user-123"
}

Parameter Description

ParameterTypeRequiredDescription
modelstringYesEmbedding model name
inputstring/arrayYesText to embed, can be a string or array of strings
encoding_formatstringNoEncoding format, supports "float" or "base64"
dimensionsintegerNoOutput vector dimensions
userstringNoUser identifier

Response Format

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "embedding": [0.0023064255, -0.009327292, ...],
      "index": 0
    }
  ],
  "model": "text-embedding-ada-002",
  "usage": {
    "prompt_tokens": 8,
    "total_tokens": 8
  }
}

Reranking Interface

POST /v1/rerank

Reorders document lists based on relevance to a query.

Request Parameters

{
  "model": "rerank-multilingual-v2.0",
  "query": "What is artificial intelligence?",
  "documents": [
    "Artificial intelligence is a branch of computer science",
    "Machine learning is a subfield of artificial intelligence",
    "Deep learning uses neural networks"
  ],
  "top_n": 3,
  "return_documents": true
}

Parameter Description

ParameterTypeRequiredDescription
modelstringYesReranking model name
querystringYesQuery text
documentsarrayYesList of documents to rank
top_nintegerNoNumber of documents to return
return_documentsbooleanNoWhether to return document content

Response Format

{
  "id": "rerank-123",
  "results": [
    {
      "index": 0,
      "score": 0.95,
      "document": "Artificial intelligence is a branch of computer science"
    },
    {
      "index": 1,
      "score": 0.87,
      "document": "Machine learning is a subfield of artificial intelligence"
    }
  ],
  "model": "rerank-multilingual-v2.0",
  "usage": {
    "total_tokens": 25
  }
}

Text-to-Speech Interface

POST /api/v1/audio/speech

Console surface only. The /v1 surface registers just GET /v1/models, POST /v1/chat/completions, POST /v1/embeddings and POST /v1/rerank — there is no /v1/... mapping for this endpoint.

Converts text into speech audio files.

Request Parameters

{
  "model": "tts-1",
  "input": "Hello, this is a test voice.",
  "voice": "alloy",
  "response_format": "mp3",
  "speed": 1.0
}

Parameter Description

ParameterTypeRequiredDescription
modelstringYesTTS model name
inputstringYesText to convert
voicestringYesVoice type
response_formatstringNoAudio format, default is mp3
speednumberNoSpeech speed between 0.25-4.0, default is 1.0

Response Format

Returns binary data of the audio file with Content-Type set according to response_format.

Speech-to-Text Interface

POST /api/v1/audio/transcriptions

Console surface only. The /v1 surface registers just GET /v1/models, POST /v1/chat/completions, POST /v1/embeddings and POST /v1/rerank — there is no /v1/... mapping for this endpoint.

Converts audio files into text.

Request Parameters

Using multipart/form-data format:

file: (audio file)
model: whisper-1
language: zh
prompt: (optional prompt)
responseFormat: json
temperature: 0

Parameter Description

ParameterTypeRequiredDescription
filefileYesAudio file
modelstringYesSTT model name
languagestringNoAudio language code
responseFormatstringNoResponse format, default is json
temperaturenumberNoSampling temperature

Response Format

{
  "text": "Transcribed text content"
}

Image Generation Interface

POST /api/v1/images/generations

Console surface only. The /v1 surface registers just GET /v1/models, POST /v1/chat/completions, POST /v1/embeddings and POST /v1/rerank — there is no /v1/... mapping for this endpoint.

Generates images based on text descriptions.

Request Parameters

{
  "model": "dall-e-3",
  "prompt": "A cute little cat playing in a garden",
  "n": 1,
  "size": "1024x1024",
  "quality": "standard",
  "response_format": "url",
  "style": "vivid",
  "user": "user-123"
}

Parameter Description

ParameterTypeRequiredDescription
modelstringYesImage generation model name
promptstringYesImage description text
nintegerNoNumber of images to generate, default is 1
sizestringNoImage dimensions, default is 1024x1024
qualitystringNoImage quality, standard or hd
response_formatstringNoResponse format, url or b64_json
stylestringNoImage style, vivid or natural
userstringNoUser identifier

Response Format

{
  "created": 1677652288,
  "data": [
    {
      "url": "https://example.com/image.png",
      "revised_prompt": "Revised prompt"
    }
  ]
}

Image Editing Interface

POST /api/v1/images/edits

Console surface only. The /v1 surface registers just GET /v1/models, POST /v1/chat/completions, POST /v1/embeddings and POST /v1/rerank — there is no /v1/... mapping for this endpoint.

Edits existing images.

Request Parameters

A JSON request body (@RequestBody ImageEditDTO.Request) — not multipart/form-data:

{
  "image": ["<image reference>"],
  "prompt": "Edit description",
  "model": "dall-e-2",
  "n": 1,
  "size": "1024x1024",
  "response_format": "url",
  "user": "user-123"
}

Contrast with /api/v1/audio/transcriptions, which is declared as multipart/form-data, and note the part name there is responseFormat (camelCase).

Response Format

{
  "created": 1677652288,
  "data": [
    {
      "url": "https://example.com/edited-image.png"
    }
  ]
}

Error Handling

Error Response Format

{
  "error": {
    "message": "Invalid request: missing required parameter 'model'",
    "type": "invalid_request_error",
    "code": "INVALID_REQUEST"
  }
}

Common Error Types

Error TypeDescription
invalid_request_error400 / 422 / other 4xx — request parameter error
authentication_error401 — authentication failed
permission_error403 — insufficient permissions
not_found_error404 — resource not found
rate_limit_error429 — rate limit / quota exceeded
api_errorevery 5xx (including 503) — gateway or downstream internal error

These are the only values the gateway emits (V1ErrorBodyMapper#errorTypeOf); overloaded_error is never produced. This OpenAI-shaped body applies to /v1/**; /api/** normally keeps the RouterResponse shape (success / message / data / errorCode / timestamp) instead — except when the response is written by the authentication filter (401 for a missing/invalid credential, 429 RATE_LIMITED_API_KEY), which emits this same {"error":{"message","type","code"}} shape on /api/** too. See Error Codes for the full exception list.

Usage Examples

cURL Examples

# Chat Completion
curl -X POST "http://localhost:8080/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "model": "gpt-3.5-turbo",
    "messages": [
      {"role": "user", "content": "Hello!"}
    ]
  }'

# Text Embedding
curl -X POST "http://localhost:8080/v1/embeddings" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "model": "text-embedding-ada-002",
    "input": "Text to embed"
  }'

Python Examples

import requests

# Configuration
base_url = "http://localhost:8080"
api_key = "your-api-key"
headers = {
    "Content-Type": "application/json",
    "X-API-Key": api_key
}

# Chat Completion
response = requests.post(
    f"{base_url}/v1/chat/completions",
    headers=headers,
    json={
        "model": "gpt-3.5-turbo",
        "messages": [
            {"role": "user", "content": "Hello!"}
        ]
    }
)
print(response.json())

# Text Embedding
response = requests.post(
    f"{base_url}/v1/embeddings",
    headers=headers,
    json={
        "model": "text-embedding-ada-002",
        "input": "Text to embed"
    }
)
print(response.json())

JavaScript Examples

const baseUrl = 'http://localhost:8080';
const apiKey = 'your-api-key';

// Chat Completion
async function chatCompletion() {
  const response = await fetch(`${baseUrl}/v1/chat/completions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': apiKey
    },
    body: JSON.stringify({
      model: 'gpt-3.5-turbo',
      messages: [
        { role: 'user', content: 'Hello!' }
      ]
    })
  });

  const data = await response.json();
  console.log(data);
}

// Text Embedding
async function embedding() {
  const response = await fetch(`${baseUrl}/v1/embeddings`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': apiKey
    },
    body: JSON.stringify({
      model: 'text-embedding-ada-002',
      input: 'Text to embed'
    })
  });

  const data = await response.json();
  console.log(data);
}