All projects
Active DevelopmentBackendSep 2026 — present

CogniGate

One endpoint in front of every model your applications use.

A self-hosted, multi-tenant LLM gateway: one key and the OpenAI API shape in front of every model your applications use. The edge gateway is Go 1.26 on Fiber v2, with provider plugins, analytics, and a conformance suite that runs in CI.

Built with

GoFiberDocker

Highlights

  • ▸Speaks the OpenAI API shape, so existing clients point at one gateway endpoint and keep working.
  • ▸Multi-tenant by design: tenants get their own keys in front of every upstream provider.
  • ▸A conformance suite runs against the gateway in CI, not just at release time.

From the README

CogniGate Social Banner

CogniGate Logo
CogniGate

One endpoint in front of every model your applications use

Self-hosted · Multi-tenant · OpenAI-compatible · Open Source

CI Status Latest Release Apache 2.0 License GitHub Stars

📖 Documentation · 🤖 AI Agents · 🐛 Report Bug · ✨ Request Feature · 💬 Discussions


What Is CogniGate?

CogniGate is a self-hosted, multi-tenant LLM gateway that sits between your applications and the models they call. Your applications hold one CogniGate key and speak the OpenAI API; your provider credentials stay inside the deployment. It is an open-source alternative to OpenRouter and LiteLLM, for teams that would rather not route their traffic — or their keys — through someone else.

Key Capabilities

FeatureDescription
OpenAI-Compatible APIPoint an existing SDK at CogniGate and change nothing else
Capability Aliasesfast, balanced, best and transcribe resolve against the live catalogue, so a client outlives the model names it was written against
Fallback ChainsAn ordered cascade per tenant, rotating keys within a provider pool before moving to the next candidate
Circuit BreakersExponential backoff on 429/5xx; an open breaker is skipped rather than retried, and the refusal names every attempt
Per-Tenant QuotasToken and spend caps with soft thresholds, plus an observe mode for sizing them before they refuse anything
Provider Keys Stay PutHeld in memory, returned by no route, written to no disk, printed in no log line
Durable Usage MeteringEvery request recorded out-of-band in PostgreSQL and readable back per tenant, model, provider or key

Architecture

                        External Clients
              OpenAI SDK / curl / LangChain / LlamaIndex
                                |
                                |  POST /v1/chat/completions
                                |  Authorization: Bearer cg-...
                                v
  +-------------------------------------------------------------+
  |             Gateway   -   Go 1.26 + Fiber v2   -   :8080     |
  |                                                             |
  |   Auth & tenant      Alias & chain      Circuit breakers     |
  |   resolution         resolution         + key rotation       |
  |                                                             |
  |   Quotas, rate limits, catalogue cache - all in memory       |
  +-------------------------------------------------------------+
           |                                    |
           | proxied request                    | async, buffered
           |                                    | POST /api/v1/usage
           v                                    v
  OpenAI-compatible providers          Analytics   -   :8081
  OpenAI, Together, Groq,              Java 25 + Spring Boot 4.1
  Fireworks, vLLM, Ollama,                        |
  Azure OpenAI, OpenRouter                        v
                                          PostgreSQL 16  -  :5432

The two processes divide along a single line: the gateway owns the request path and holds its configuration in memory, and analytics owns everything durable. A request is never blocked on metering — if analytics is down the gateway keeps serving traffic and buffers what it cannot deliver.


Quick Start

Prerequisites

  • Docker & Docker Compose (v2+)
  • No Java or Go installation required for running — everything is containerized

One-Command Setup

# Linux / macOS
curl -sSL https://cognigate.vkrishna04.me/install.sh | bash
# Windows (PowerShell)
irm https://cognigate.vkrishna04.me/install.ps1 | iex

The installer clones the repository, generates credentials and starts the stack. To do the same by hand — or if you would rather read the script before running it, which you should:

git clone https://github.com/Life-Experimentalist/CogniGate.git
cd CogniGate
./setup.sh --dev --detach        # PowerShell: .\setup.ps1 -Mode dev -Detach

setup.sh expects to be run from inside a checkout; it is the second half of what the installer does, not a substitute for the clone.

This single command:

  1. ✅ Copies .env.example → .env
  2. ✅ Generates a GATEWAY_BOOTSTRAP_KEY and an ANALYTICS_TOKEN
  3. ✅ Builds and starts the three services (Gateway, Analytics, PostgreSQL), waiting until each reports healthy

Or Pull the Published Images

Every push to main publishes both services to the GitHub Container Registry and to Docker Hub, built for linux/amd64 and linux/arm64. Both are public, so no docker login is needed to pull them:

docker pull ghcr.io/life-experimentalist/cognigate-gateway:main
docker pull ghcr.io/life-experimentalist/cognigate-analytics:main

The same digests are on Docker Hub, for environments that already authenticate there:

docker pull vkrishna04/cognigate-gateway:main
docker pull vkrishna04/cognigate-analytics:main

docker-compose.yml names those images alongside its build contexts, so a checkout that does not want to compile anything can fetch them instead:

git clone https://github.com/Life-Experimentalist/CogniGate.git
cd CogniGate
cp .env.example .env
openssl rand -hex 24   # -> GATEWAY_BOOTSTRAP_KEY in .env
openssl rand -hex 32   # -> ANALYTICS_TOKEN in .env
docker compose pull && docker compose up -d --wait

The tags are main for the tip of the default branch and sha-<commit> to pin one commit of it. latest currently resolves to the same image as main; once the first release is tagged it will follow releases instead, so main is the tag that keeps meaning the branch either way. Releases will additionally carry X.Y.Z and X.Y — none has been cut yet.

Checking where an image came from

Both images are published with signed build provenance and an SPDX SBOM, attached to the image digest and stored in the registry beside it. That attestation lives on the GitHub Container Registry copy, which is where the verification below reads it from. Nothing needs to be cloned to check one:

gh attestation verify oci://ghcr.io/life-experimentalist/cognigate-gateway:main --repo Life-Experimentalist/CogniGate

That confirms the image was built by this repository's workflow at a specific commit, rather than by whoever else could push a tag with the same name. Release binaries carry the same guarantee; the command is in each release's notes.

Verify It's Running

curl -s http://localhost:8080/healthz
# → {"status":"ok"}

Your First API Key

The admin plane is reached with an Authorization: Bearer credential, exactly like the data plane. Before any tenant exists there is only one such credential: the bootstrap key that setup generated into .env.

BOOTSTRAP=$(grep '^GATEWAY_BOOTSTRAP_KEY=' .env | cut -d= -f2-)

# 1. Create a tenant.
curl -s -X POST http://localhost:8080/admin/v1/tenants \
  -H "Authorization: Bearer $BOOTSTRAP" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-org"}'
# → {"id":"ten_...","name":"my-org","status":"active",...}

# 2. Register an upstream provider for that tenant. `keys` is a pool — CogniGate
#    rotates within it before it ever falls back to another model.
curl -s -X POST http://localhost:8080/admin/v1/tenants/ten_.../providers \
  -H "Authorization: Bearer $BOOTSTRAP" \
  -H "Content-Type: application/json" \
  -d '{"name":"openai","kind":"openai","base_url":"https://api.openai.com/v1","keys":["sk-..."]}'

# 3. Mint a data-plane key. The secret is returned once and never again.
curl -s -X POST http://localhost:8080/admin/v1/tenants/ten_.../keys \
  -H "Authorization: Bearer $BOOTSTRAP" \
  -H "Content-Type: application/json" \
  -d '{"name":"first","plane":"data"}'
# → {"key":{"id":"key_...","prefix":"cg-mP6XKz-R",...},"secret":"cg-...","warning":"..."}

Only a SHA-256 hash and the displayable prefix are stored, so nothing in CogniGate can recover the secret later. If it is lost, revoke the key and mint a new one.

The control plane is in memory. Tenants, keys, providers, aliases, routes and quotas live in the gateway process. Postgres holds usage records and nothing else, so restarting or recreating the gateway container — a docker compose pull, a config change, a crash — takes the three steps above with it, and the first sign is a 401 on a key that worked a minute ago. Keep those calls in a script. Making the control plane durable is known work, not a surprise.

These examples assume a POSIX shell. In PowerShell, use Invoke-RestMethod — PowerShell rewrites the arguments it passes to a native executable, and the inner double quotes of a JSON body do not survive that rewrite, so curl.exe -d '{"name":"my-org"}' sends {name:my-org} and the gateway rejects it:

$b = (Select-String -Path .env -Pattern "^GATEWAY_BOOTSTRAP_KEY=").Line.Split("=",2)[1]
Invoke-RestMethod -Method Post -Uri http://localhost:8080/admin/v1/tenants `
  -Headers @{ Authorization = "Bearer $b" } -ContentType "application/json" `
  -Body '{"name":"my-org"}'

Your First Request

curl -s http://localhost:8080/v1/models -H "Authorization: Bearer cg-..."

curl -s -X POST http://localhost:8080/v1/chat/completions \
  -H "Authorization: Bearer cg-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello, CogniGate!"}]}'

Every tenant is created with the portable aliases fast, balanced, best and transcribe. Sending "model": "balanced" resolves to whatever currently fits that constraint in the tenant's catalog, so a client can be written once and keep working as providers ship new models.

An ordered fallback chain is a routing rule:

curl -s -X PUT http://localhost:8080/admin/v1/tenants/ten_.../routing-rules \
  -H "Authorization: Bearer $BOOTSTRAP" \
  -H "Content-Type: application/json" \
  -d '{"match":"gpt-4o","chain":["gpt-4o","llama-3.3-70b","gpt-4o-mini"]}'

Configuration

All configuration is documented in:

Environment Variables

VariableDefaultDescription
GATEWAY_BOOTSTRAP_KEY(auto-generated)Admin-plane bootstrap credential; min 16 characters, no default
ANALYTICS_TOKEN(auto-generated)Shared secret between the two processes. Analytics refuses to start without it rather than expose an unauthenticated metering API
SPRING_DATASOURCE_URLjdbc:postgresql://postgres-db:5432/cognigatePostgreSQL JDBC URL
SPRING_DATASOURCE_USERNAMEcognigate_userDB username
SPRING_DATASOURCE_PASSWORDcognigate_passDB password
POSTGRES_DBcognigateDatabase name

The gateway itself is configured from cognigate.config.yml, which docker-compose.yml bind-mounts read-only into the container: edit it and recreate the gateway, and the new values are live. Recreating the gateway also empties the in-memory control plane, so re-provision afterwards. The settings below can additionally be set from the environment. The environment always wins over the file, and the file over the defaults. Each is also read without the CG_ prefix, for platforms that inject a bare PORT.

VariableSets
CG_PORTgateway.port
CG_ADMIN_BOOTSTRAP_KEYadmin.bootstrap_key — what docker-compose.yml passes
CG_ANALYTICS_URLanalytics.base_url
CG_ANALYTICS_TOKENanalytics.token
CG_METRICS_TOKENmetrics.token
CG_LOG_LEVELlog.level
CG_QUOTA_ENFORCEMENTquotas.enforcement (on or observe)
CG_CACHE_ENABLEDcache.enabled

Documentation

Full documentation is available at https://cognigate.vkrishna04.me.

SectionDescription
Getting StartedInstallation, first run, quick test
ArchitectureSystem design, data flows, component interactions
API ReferenceAll endpoints, request/response schemas
RoutingAliases, fallback chains, breakers, and how a model name is resolved
Integrating an AppPutting CogniGate in front of software that already calls an LLM API
AI AgentsThe agent skill, and what it will and will not do on your machine
SecurityCredential handling, the two planes, tenant isolation
Usage & CostWhat is metered, how to read it back, where the cost figure comes from
DeploymentProduction hardening, TLS, scaling, and what it still owes you

Let an Agent Do It

CogniGate ships an agent skill — one markdown file that teaches a coding agent to install the gateway, provision a tenant, register providers, wire an existing application into it, and diagnose it when it breaks. Install it once and it is there in every future session.

In Claude Code, this repository is a plugin marketplace:

/plugin marketplace add Life-Experimentalist/CogniGate
/plugin install cognigate@cognigate

Anywhere else, it is one file over HTTPS:

mkdir -p ~/.claude/skills/cognigate && curl -fsSL https://cognigate.vkrishna04.me/skill/SKILL.md -o ~/.claude/skills/cognigate/SKILL.md

Then say "set up CogniGate and point my app at it". For other agents, the file is plain markdown — drop it wherever your tool loads instructions from.

Read it first, it is short: skill/SKILL.md. It refuses to print secrets, refuses to wipe the database to fix a container, and refuses to report success from a script's own output. Details and the verification commands are in AI Agents.

There is also a shorter one-URL bootstrap at prompt.md, and a whole-repository briefing in COGNIGATE_AI_CONTEXT.md for questions about the code itself.


Integrating an Existing Application

If it already speaks the OpenAI API, the integration is two settings — a base URL and a key. No SDK, no adapter, no change to any request or response:

client = OpenAI(
    base_url=os.environ["COGNIGATE_URL"] + "/v1",
    api_key=os.environ["COGNIGATE_API_KEY"],   # the cg- secret
)

Then delete the provider credential from the application, and rotate it at the provider if it was ever committed. The full procedure — finding the call sites, one key per service per environment, moving model names behind aliases, and how to prove the traffic is actually going through the gateway — is in Integrating an Application.


Adding a Provider

CogniGate ships three provider kinds, and registering any of them is a single admin call that needs no code.

openai covers every service that reimplements the OpenAI wire format: Together, Groq, Fireworks, Azure OpenAI, OpenRouter, vLLM, Ollama, LM Studio. Only the base URL differs.

curl -s -X POST http://localhost:8080/admin/v1/tenants/ten_.../providers \
  -H "Authorization: Bearer $BOOTSTRAP" \
  -H "Content-Type: application/json" \
  -d '{"name":"local","kind":"openai","base_url":"http://localhost:11434/v1","keys":["unused"]}'

gemini and anthropic are that same adapter pointed at each vendor's own OpenAI-compatible endpoint. They carry its base URL as a default, so base_url is optional for them, and they report the vendor's name rather than "openai" in logs, metrics and X-CogniGate-Served-By.

curl -s -X POST http://localhost:8080/admin/v1/tenants/ten_.../providers \
  -H "Authorization: Bearer $BOOTSTRAP" \
  -H "Content-Type: application/json" \
  -d '{"name":"gemini","kind":"gemini","keys":["...","..."]}'

keys is a pool, and key_strategy decides which key a request starts from. round_robin, the default, advances one key per request, so several keys from one vendor share a steady load instead of piling onto the first until it throttles. failover pins every request to the first key and keeps the rest for when it starts refusing, which is what a paid key backed by a free one wants. Under either strategy a 429 walks the whole pool before the fallback chain moves on.

A provider that speaks a protocol none of the three cover needs a translating proxy in front of it. There is no runtime plugin mechanism, and adding a native adapter is a code change in gateway/internal/provider/.


Tech Stack

ComponentTechnology
Edge GatewayGo 1.26, Fiber v2
Analytics EngineJava 25 LTS, Spring Boot 4.1, Hibernate, virtual threads (Project Loom)
DatabasePostgreSQL 16
Container RuntimeDocker, Docker Compose
Docs SiteNext.js 16, Tailwind CSS 4, Three.js / React Three Fiber

Brand

The mark, the palette and the exact image-model prompts used to generate them live in docs/public/brand/logo-prompt.md — four prompts (icon, favicon, wordmark lockup, social banner), the seven hex values nothing may drift off, and the checks a generated logo has to survive before it ships. docs/public/logo.png and docs/public/banner.png are served at /logo.png and /banner.png, so replacing either is a file swap.


Contributing

We welcome contributions! Please read our Contributing Guide and follow our Code of Conduct.

For security vulnerabilities, please see our Security Policy — report privately, never via public issues.


License

Copyright 2026 VKrishna04 and Life Experimentalist

Licensed under the Apache License, Version 2.0. See LICENSE for the full text.


Built with ❤️ by VKrishna04 and Life Experimentalist

Local Storage Notice

This site uses local storage to cache GitHub data for better performance, and an anonymous visit counter. No personal data is stored or shared.