Skip to content

About

One OpenAI-compatible endpoint for a whole company, with API keys requested and approved in a portal, a budget per key, and data confidentiality rules enforced model by model.

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

 

History

575 Commits

Folders and files

Repository files navigation

LINAGORA

LINAGORA AI Gateway

One OpenAI-compatible endpoint for a whole company, with API keys requested and approved in a portal, a budget per key, and data confidentiality rules enforced model by model.

License: AGPL-3.0 LiteLLM community edition Next.js PostgreSQL Languages: English, French

Features · Screenshots · Architecture · Getting started · Integration API · Contributing · Changelog


Model catalog of the portal, one card per confidentiality level

Overview

LINAGORA AI Gateway gives every LINAGORA employee access to generative AI models through a single OpenAI-compatible endpoint, while keeping control over which data goes to which model.

An employee asks for an API key in the portal and declares the confidentiality level of the data they will process. An administrator, or the manager of their team, approves the request with a budget and a validity period. Each level only opens the models whose hosting guarantees fit it: models served worldwide for public information, models hosted in France for customer and personal data.

The gateway runs the community edition of LiteLLM Proxy. The portal, built with Next.js, handles requests, approvals, keys, budgets, teams and reporting. Everything is self-hosted on a single virtual machine with Docker Compose.

Features

For employees

  • Model catalog by confidentiality level. N1 Public, N2 Internal, N3 Confidential and Experimental: for each level, what may and may not be entrusted to it, the hosting guarantees, and each model's execution region and price per million tokens.
  • Key requests with a declared data level. Team, level, models, reason, project, requested duration and budget, with an explicit commitment not to submit data above the declared level. The budget is prefilled with the default value; a higher budget must be justified.
  • Self-service keys. Pick up an approved key, follow spend against budget and the expiry date, ask for a higher budget, renew, replace a lost key or revoke it, with ready-to-use examples in curl, Python and JavaScript.
  • Code assistants. Set up OpenCode or Claude Code from your issued keys: the portal builds the OpenCode configuration, or one Claude Code launcher per key, with a step-by-step guide for Ubuntu. The key never goes through the portal: the commands ask for it in the terminal and keep it outside the configuration.
  • Teams and subscriptions. Ask to join a team; as an exception, request an individual subscription (ChatGPT, Claude, Kimi…), granted after an enhanced review, with reimbursement of declared charges.
  • Service status. The current state of each catalog model (operational, degraded, incident, not monitored), checked regularly by the gateway, so that employees know an incident is already being handled.
  • Bilingual. The portal and its email notifications are available in English and French.

For administrators and team managers

  • Approval workflow. A review queue, the policy checks of each request, the budget, budget period and validity of the key; ask the requester for more information, approve or refuse. Requests for a higher budget on an issued key go through the same queue. Team managers approve their team's requests, never their own.
  • Administration. Keys, subscriptions, teams and managers, employees, the catalog (display names and descriptions in both languages, maximum level, visibility), default key values, and the accounting export of subscription charges.
  • Reporting. Apache Superset dashboards of costs, requests and tokens by team, model and level.
  • Access checkpoint. The portal's single sign-on session and uid allowlists guard the LiteLLM console and Superset, through Caddy's forward_auth.
  • Daily task. Expiry reminders and expirations, run by the server's cron.
  • Model monitoring. Every model visible in the catalog is probed through the same route as employees, every 10 minutes by default (SUPERVISION_INTERVAL_MINUTES); administrators get an email after two consecutive failures and another on recovery, and a Supervision tab shows the state of each model. New models are picked up automatically. Image models and models that search the web on every request are not probed, since each probe would be billed.

Gateway

  • One endpoint. https://ai-api.linagora.com/v1, compatible with any OpenAI SDK, and with the Anthropic Messages API (/v1/messages) used by Claude Code; models are declared in the database and priced in euros.
  • Spend in euros. Every request is charged to its key at the model's declared euro price, streamed or not, including tokens read from the cache and, for search models, the web search billed with each request.
  • Model allowlist. OpenRouter models are allowlisted one by one, each routed only to the providers of its execution zone, falling back from one to another when the first is saturated; models served outside the EU only go to providers that do not collect the data, under a price cap where needed. Confidential data goes to models hosted in France on OVHcloud AI Endpoints.
  • Guard hook. Requests may not switch model or provider through their parameters (fallbacks, provider preferences, plugins, presets), nor use the image and video APIs whose cost would escape key budgets.
  • Privacy by default. Prompts and responses are never stored in spend logs, and per-request details are kept for 90 days.
  • Fast revocation. A revoked key stops working within seconds.

Integration API

  • Requests from other applications. Applications declared by the administrators, such as a team management tool, act for one employee at a time: they read the employee's teams, the catalog, their requests and keys, submit key or team access requests, pick up an approved key, replace or revoke a key and prepare its renewal. Requests are still approved in the portal, with the same rules, emails and audit trail.
  • Defense in depth. Short-lived JWTs signed by the integration (EdDSA with Ed25519, or RS256), scopes, IP allowlist, rate limit and an on/off switch per integration.
  • Documented contract. OpenAPI 3.1 contract, Swagger UI in the portal and an integration guide in English and French.

Screenshots

Request a key. The declared data level filters the models that can be requested.

Key request form

My keys. Endpoint, expiry, spend against budget, renewal, replacement and revocation.

Issued keys of an employee

Approval queue. Pending requests, then the archive of processed ones.

Queue of requests awaiting validation

Request review. Policy checks, then the budget and validity of the key.

Review of a key request with its policy checks

Integration API. The contract in Swagger UI, which can be tried with a signed token.

Integration API documentation with Swagger UI

Integrations. Scopes, allowed addresses, rate limit and public keys of each application.

Integrations declared by the administrators

The screenshots are taken from the development environment, with fictional people and data, by npm run captures (see portal/dev/captures).

Architecture

flowchart LR
    tools["Employees' tools<br>SDKs, IDEs, scripts"]
    browser["Browser"]
    apps["Declared integrations"]

    subgraph vm["Virtual machine, Docker Compose"]
        caddy["Caddy<br>TLS, routing, IP filtering"]
        portal["Portal<br>Next.js"]
        litellm["LiteLLM Proxy<br>community edition"]
        superset["Apache Superset"]
        postgres[("PostgreSQL")]
        valkey[("Valkey")]
    end

    sso["LemonLDAP::NG<br>OpenID Connect"]
    providers["Model providers<br>OpenRouter, OVHcloud AI Endpoints…"]

    tools -->|"ai-api.linagora.com/v1"| caddy
    browser -->|"ai-gateway.linagora.com"| caddy
    apps -->|"/api/v1, signed JWT"| caddy
    caddy --> portal
    caddy --> litellm
    caddy -->|"/stats"| superset
    portal -->|"admin API"| litellm
    portal -->|"single sign-on"| sso
    litellm --> providers
    portal --> postgres
    litellm --> postgres
    superset --> postgres
    superset --> valkey
Loading

Only Caddy publishes ports. Every other service stays on the internal Docker network.

URL Service
https://ai-api.linagora.com/v1 OpenAI-compatible endpoint (LiteLLM)
https://ai-gateway.linagora.com/ Portal
https://ai-gateway.linagora.com/api/v1/ Integration API of the portal
https://ai-gateway.linagora.com/admin/ LiteLLM console and admin API: allowed addresses, administrators only
https://ai-gateway.linagora.com/stats/ Superset: administrators and reporting readers

Stack

Component Role
Caddy 2 Reverse proxy, automatic TLS, IP filtering, access checkpoint
LiteLLM Proxy, community edition OpenAI-compatible gateway: keys, budgets, spend logs, providers
Portal: Next.js 16, Auth.js, Prisma, next-intl Requests, approvals, keys, teams, subscriptions, catalog, integration API
PostgreSQL 18 Databases of LiteLLM and of the portal, reporting views
Apache Superset 6 and Valkey 9 Reporting dashboards and their cache
LemonLDAP::NG Single sign-on (OpenID Connect)

Every third-party image is pinned by tag and digest, in infra/.env.example and the portal's Dockerfile; LiteLLM releases are checked with their cosign signature before use.

Getting started

Run the portal locally

Requirements: Docker with Compose, Node.js 24, jq and curl. The development environment runs entirely on your machine, with fictional data and no production key: LiteLLM serves demo models whose answers are mocked.

cd portal
docker compose -f dev/docker-compose.yml up -d  # PostgreSQL, LiteLLM, mock OpenID provider, Mailpit, Caddy
./dev/seed-litellm.sh                           # demo models priced in euros and an "R&D" team
cp .env.example .env                            # development values
npm install
npx prisma generate && npx prisma migrate deploy
npm run dev                                      # http://localhost:3100

To sign in, the mock OpenID provider asks for a user name (the uid) and claims, such as {"email": "jdoe@example.org", "name": "Jane Doe"}. The uids listed in PORTAL_ADMIN_UIDS (.env) are administrators.

Service Address
Portal http://localhost:3100
Portal behind Caddy, as in production (integration API) http://127.0.0.1:54600
LiteLLM admin API http://127.0.0.1:54400/admin
Mailpit, the emails sent by the portal http://127.0.0.1:54825

Deploy

docs/RUNBOOK-INSTALL.md (in French) describes the installation on a virtual machine, phase by phase, each with its checks: host preparation, DNS, secrets generated on the server, version pinning and verification, core services, single sign-on, reporting and backups, acceptance, then the portal. infra/ holds the files deployed on the server.

Production runs published versions only: each one is tagged vX.Y.Z, described in the changelog and listed on the releases page.

Using the gateway

Once a key is picked up in the portal, any OpenAI-compatible client works. The model names are those of the catalog.

curl https://ai-api.linagora.com/v1/chat/completions \
  -H "Authorization: Bearer $LINAGORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_NAME", "messages": [{"role": "user", "content": "Hello!"}]}'
import os
from openai import OpenAI

client = OpenAI(base_url="https://ai-api.linagora.com/v1", api_key=os.environ["LINAGORA_API_KEY"])
reply = client.chat.completions.create(model="MODEL_NAME", messages=[{"role": "user", "content": "Hello!"}])
print(reply.choices[0].message.content)

Integration API

The portal exposes a REST API under /api/v1 so that other applications can submit requests on behalf of an employee. An administrator declares each application in the portal, with its scopes, allowed addresses, rate limit and public keys; the application then signs a short-lived token for each employee it acts for.

  • Integration guide: English, French, also shown in the portal under API.
  • Contract: openapi-v1.json (OpenAPI 3.1), browsable by signed-in employees in Swagger UI at /documentation/api, and returned by the API itself at GET /api/v1/openapi.json.

In development, node dev/integration-demo.mjs installer declares a demo integration, and node dev/integration-demo.mjs jeton <uid> signs a token with its private key, which stays out of the repository:

curl -H "Authorization: Bearer $(node dev/integration-demo.mjs jeton jdoe)" http://127.0.0.1:54600/api/v1/me/teams

Tests

Run from portal/. Integration and end-to-end tests need the development environment.

Command Scope
npm test Unit tests: business rules, OpenID claims, integration tokens, address allowlists, rate limit, API contract
npm run test:int Integration tests: services against the portal_test database, contract with the development LiteLLM
npm run test:e2e End-to-end journeys in a browser (Playwright), emails checked in Mailpit
npm run test:coverage Unit and integration tests with code coverage; the run fails below the thresholds of vitest.config.ts
npm run lint ESLint
npm run captures Screenshots of this README

On GitHub Actions, every pull request and every push to main runs lint, types and unit tests, the integration tests with coverage, and the end-to-end journeys against a production build of the portal (next start).

Repository layout

Path Content
infra/ Files deployed on the server: Docker Compose, Caddy, LiteLLM configuration and hooks, PostgreSQL initialization and reporting views, Superset, operation scripts (bootstrap, secrets, backups and restore tests, model synchronization, smoke tests)
portal/ The portal: Next.js application, Prisma schema and migrations, tests, development environment
docs/ Installation runbook, pointer to the integration guide, screenshots
.github/ Continuous integration workflow (GitHub Actions)

Roadmap

  • Traces of the gateway's calls with Langfuse, under /traces/.

Contributing

Contributions are welcome: see CONTRIBUTING.md. The code base uses French for its domain vocabulary, comments, commit messages and most documentation; the portal and the integration guide are available in English and French.

Security

Please do not report security vulnerabilities through public issues. See SECURITY.md to report them privately.

License

Copyright © 2026 LINAGORA.

LINAGORA AI Gateway is free software, released under the GNU Affero General Public License v3.0.

About

One OpenAI-compatible endpoint for a whole company, with API keys requested and approved in a portal, a budget per key, and data confidentiality rules enforced model by model.

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages