Skip to content

LLM Council HTTP API

This document describes the HTTP REST API for LLM Council. The API is available via the optional [http] extra.

Installation

pip install "llm-council-core[http]"

Starting the Server

# Default: 0.0.0.0:8000
llm-council serve

# Custom host and port
llm-council serve --host 127.0.0.1 --port 9000

OpenAPI Specification

The server provides auto-generated OpenAPI documentation:

  • Interactive docs (Swagger UI): http://localhost:8000/docs
  • ReDoc: http://localhost:8000/redoc
  • OpenAPI JSON: http://localhost:8000/openapi.json

API Reference

Health Check

Check if the server is running.

GET /health

Response:

{
  "status": "ok",
  "service": "llm-council-local"
}

Run Council Deliberation

Execute the full 3-stage council deliberation process.

POST /v1/council/run

Request Body:

{
  "prompt": "What is the best approach for implementing user authentication?",
  "models": ["openai/gpt-5.4", "anthropic/claude-opus-4.8"],
  "api_key": "sk-..."
}

models and api_key are optional — omit api_key when OPENROUTER_API_KEY is set on the server (preferred; see warning below).

Field Type Required Description
prompt string Yes The question for the council to deliberate
models string[] No Specific models to use (defaults to configured council)
api_key string No API key for the default gateway (falls back to env vars)
gateway string No Gateway to use: openrouter, requesty, or direct

Response:

{
  "stage1": [
    {
      "model": "openai/gpt-4",
      "response": "Individual model response..."
    }
  ],
  "stage2": [
    {
      "model": "anthropic/claude-3-opus",
      "ranking": "FINAL RANKING:\n1. Response A\n2. Response B",
      "parsed_ranking": {
        "ranking": ["Response A", "Response B"],
        "scores": {"Response A": 9, "Response B": 7}
      }
    }
  ],
  "stage3": {
    "model": "chairman",
    "response": "Synthesized final answer..."
  },
  "metadata": {
    "label_to_model": {
      "Response A": "openai/gpt-4",
      "Response B": "anthropic/claude-3-opus"
    },
    "aggregate_rankings": [
      {"model": "openai/gpt-4", "borda_score": 0.85, "votes": 3}
    ]
  }
}

Error Responses:

Status Description
400 API key required but not provided
422 Validation error (e.g., missing prompt)
500 Internal server error

Authentication

The local development server uses BYOK (Bring Your Own Keys) with multiple gateway options:

Gateway Options

Gateway Environment Variable Description
OpenRouter (default) OPENROUTER_API_KEY 100+ models via single key
Requesty REQUESTY_API_KEY BYOK mode, analytics
Direct ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY Direct provider APIs

Configuration

  1. In request body: Pass api_key field (OpenRouter key)
  2. Environment variables: Set gateway-specific keys
# OpenRouter (default)
export OPENROUTER_API_KEY=sk-or-v1-...

# Or use direct provider APIs
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
export GOOGLE_API_KEY=...
export LLM_COUNCIL_DEFAULT_GATEWAY=direct

Request body takes precedence over environment variables.

Protocol Compatibility

This API protocol is shared between:

  • llm-council[http] (OSS): Local development, stateless
  • council-cloud (Proprietary): Production, with auth/billing/caching

To migrate from local to production, simply change the base URL:

// Development
const baseUrl = "http://localhost:8000";

// Production
const baseUrl = "https://api.council.cloud";

Examples

cURL

# Using environment variable
export OPENROUTER_API_KEY=sk-...
curl -X POST http://localhost:8000/v1/council/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "What is the best database for this use case?"}'

# Using API key in request
curl -X POST http://localhost:8000/v1/council/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "What is the best approach?", "api_key": "sk-..."}'

Python

import httpx

response = httpx.post(
    "http://localhost:8000/v1/council/run",
    json={
        "prompt": "What is the best approach for X?",
        "api_key": "sk-..."
    }
)
result = response.json()
print(result["stage3"]["response"])  # Final synthesized answer

JavaScript/TypeScript

const response = await fetch("http://localhost:8000/v1/council/run", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    prompt: "What is the best approach for X?",
    api_key: "sk-..."
  })
});
const result = await response.json();
console.log(result.stage3.response);  // Final synthesized answer

Stream Council Deliberation (SSE)

Watch the deliberation live (ADR-046). Every event carries the v1 envelope (v, session_id, ts, monotonic seq).

GET /v1/council/stream?prompt=...&stream_tokens=false
Event Payload When
stage1.response model, response, latency_ms, usage each model's answer, as it lands
stage2.review reviewer, ranking, parse_ok each peer review
consensus.early_termination ADR-044 payload early consensus (flag-on)
stage3.start chairman synthesis begins
synthesis.delta text chairman tokens (opt-in: stream_tokens=true)
council.complete / council.error full result / error terminal
curl -N "http://localhost:8000/v1/council/stream?prompt=What+is+AI&stream_tokens=true"

Do not pass api_key in the query string

Query strings land in server/proxy logs and browser history. Prefer the OPENROUTER_API_KEY environment variable on the server (BYOK), and keep LLM_COUNCIL_API_TOKEN auth in headers. The api_key request/query parameter exists for single-user local setups only.

Request Fields (POST /v1/council/run)

Beyond prompt/models/api_key, the endpoint accepts verdict_type (synthesis | binary | tie_breaker — ADR-025b Jury Mode) and include_dissent (extract minority opinions). Responses include a typed usage field (ADR-011 cost transparency: per-stage, per-model, total — cost_known distinguishes real provider costs from unknowns).

MCP Server Card (Discovery)

Public discovery metadata generated from the live tool registry (ADR-045, SEP-2127) — unauthenticated, like /health:

GET /server-card
GET /.well-known/mcp/server-card.json

Design Principles

Per ADR-009:

  1. Stateless: No database, no persistent storage
  2. Single-tenant: No multi-user authentication
  3. BYOK: API keys passed in request or read from environment
  4. Ephemeral: Logs go to stdout only

For stateful features (auth, caching, audit logs), see council-cloud.