Architecture

The C4 model

From one box on a context diagram down to the registries that make the processing stack extensible — and then out again to what actually runs in Kubernetes.

L1

System context

At this level APIHUB is a single box. What matters is who talks to it and what it cannot run without. Only PostgreSQL and the image registries are mandatory; every other dependency is opt-in, which is what makes an air-gapped installation practical.

C4 · level 1 — five kinds of user, six external dependencies
APIHUB system context: five kinds of user on the left, the platform in the centre, and six external dependencies on the right

Who uses it

  • API Developer Publishes and reviews
  • API Consumer Reads and tries
  • Administrator Roles, identity, rules
  • CI/CD pipeline Publishes with a key
  • AI assistant Queries over MCP

What it depends on

  • PostgreSQL 14+ Required
  • S3 / MinIO Optional
  • Identity provider Optional
  • LLM API Optional
  • Kubernetes clusters Optional
  • GitHub · ghcr.io · npm Required
L2

Containers

Everything that gets deployed, arranged by tier. Traffic runs top to bottom: nginx is the only entry point, and the backend is the only writer to the catalogue database. The backend never pushes work to a worker — workers poll, so a worker that dies simply stops polling and its task returns to the queue.

C4 · level 2 — the APIHUB namespace, plus an agent in each inspected cluster
APIHUB container diagram: nginx and the two single-page apps on top, the backend beneath, then the four satellite services, the data stores, and the agent in a remote cluster

The seven containers in detail

APIHUB Backend

:8080 qubership-apihub-backend
Technology
Go 1.26 · gorilla/mux · go-pg
State
apihub database, optional S3
Owns
39 REST controllers, MCP server, AI chat, cron cleanup, database migrations

APIHUB UI

:8080 qubership-apihub-ui
Technology
React 18 · MUI 5 · nginx 1.28
State
Stateless
Owns
Portal and Agents single-page apps plus the reverse proxy in front of everything

Build Task Consumer

:3000 qubership-apihub-build-task-consumer
Technology
Node 24 · NestJS 10
State
Stateless — scale to 3–6
Owns
Runs PackageVersionBuilder: parse, normalize, diff, changelog, export

API Linter Service

:8080 qubership-api-linter-service
Technology
Go 1.26 · Spectral · OpenAI
State
apihub_linter database
Owns
Spectral and AI engines, rulesets, bulk validation, version scoring, MCP endpoint
Technology
Go 1.26
State
agents database
Owns
Agent registry and heartbeats, discovery orchestration, snapshot promotion

APIHUB Agent

:8080 qubership-apihub-agent
Technology
Go 1.26 · client-go
State
Stateless
Owns
Six discovery runners in parallel per service, plus a proxy into the cluster

Java Task Consumer

:3000 qubership-apihub-java-task-consumer
Technology
Java 21 · japicmp · jdeps
State
Stateless
Owns
japicmp and jdeps pipelines; the backend java-builders queue is specified only
L3

Components

One level down, inside the containers that are worth opening up: the backend, the web interface, the two pull-based workers, the linter, and the discovery pair.

Inside the backend

One Go process with six clearly separated concerns. This is the busiest container in the system, and the reason it is fair to call APIHUB a micro-monolith.

HTTP edge

  • Router + middleware

    gorilla/mux, Prometheus

  • Health / system info

    /live, /ready

Security

  • Auth strategies

    go-guardian union

  • IDP manager

    SAML · OIDC · LDAP

  • Roles & permissions

    system · package · custom

Catalogue

  • Packages

    Workspaces, groups

  • Versions

    Revisions, references

  • Operations

    Tags, groups

  • Comparisons

    Changes, summaries

  • Contracts

    DDL and MCP

  • Search

    v3 / v4 full text

Build & export

  • Build service

    Queue, ownership

  • Build results

    Unpack, store

  • Export

    xlsx, ZIP, async

  • Transformation

    Merged specs

  • Notifications

    Olric topic

AI & integration

  • MCP server

    Streamable HTTP

  • AI chat

    SSE, tool loop

  • Playground proxy

    Allowed hosts

Operations & data

  • Cleanup jobs

    8 cron schedules

  • Migrations

    Schema + operations

  • Repositories

    ~26, go-pg

  • Object storage

    minio-go

Inside the web interface

One nginx container serves two applications and routes everything else onward. Buffering is disabled on the MCP and chat routes so that streams reach the client as they are produced.

nginx · the single entry point

/portal and /agents → static · /api → backend · /api-linter → linter · /agents-backend → agents backend

Portal SPA

packages/portal

  • · Catalogue navigation
  • · Version pages: operations, changes, deprecated, documents
  • · Compare versions and operations
  • · Dashboard editor and publish dialog
  • · Settings, roles, rulesets, tokens
  • · AI chat and contracts tab

Agents SPA

packages/agents

  • · Agents and namespaces
  • · Service discovery results
  • · Snapshots and promotion
  • · Namespace security checks

Shared package

packages/shared

  • · Entities: api-types, contract-types
  • · Hooks and react-query clients
  • · MUI theme and widgets
  • · Storybook showcase

Rendering is delegated to shared web components

apispec-view operation-view diff-operation-view rest-playground class-view api-doc-viewer

The two workers

Both follow the same pull pattern: ask the backend for a task, do the work, post the result back. One of them is in production; the other is waiting for its backend endpoints.

Build Task Consumer

qubership-apihub-build-task-consumer

  1. 1

    Task poller

    An RxJS interval asks /api/v2/builders/{id}/tasks

  2. 2

    Schema validation

    ajv over the build configuration

  3. 3

    PackageVersionBuilder

    api-processor: 7 builders, 10 strategies

  4. 4

    Resolvers

    Fetch previous versions from the backend

  5. 5

    Result sender

    ZIP to /api/v3/…/publish/{id}/status

Live and scaled in production

Java Task Consumer

Pending

qubership-apihub-java-task-consumer

  1. 1

    Task poller

    Asks /api/v2/java-builders/{id}/tasks

  2. 2

    Build executor

    Maps a build type to a pipeline

  3. 3

    jdiff-engine

    ApiReport · ApiDiff · UpgradeImpact

  4. 4

    Jar resolver

    Maven coordinates or OCI image layers

  5. 5

    Result packager

    report.json inside a ZIP

Backend endpoints not implemented yet

Inside the linter

Event-driven: a publish on the bus starts the whole chain, with no pipeline wiring anywhere. The service also exposes an MCP endpoint of its own, so an agent can ask what is wrong with a version.

  1. 01

    Publish event

    Olric topic version-published

  2. 02

    Version task

    Fetch documents from the backend

  3. 03

    Document tasks

    Fan out into a worker pool

  4. 04

    Linter selector

    Engine per API type and package

  5. 05

    Result + score

    Stored in apihub_linter

Spectral engine

  • · Bundled CLI binary in the image
  • · Rulesets managed in the Portal
  • · OpenAPI and AsyncAPI documents
  • · The default path for every publish

AI engine

  • · OpenAI-compatible client
  • · Rate limited, opt-in per package
  • · Catches what rules cannot express
  • · Disabled by default

Vacuum engine

Not wired
  • · Binary and rules ship in the image
  • · Not wired into the selector
  • · Looks available from outside
  • · Worth a decision: use or remove

The discovery pair

Agents Backend stays in the APIHUB cluster. The Agent is deployed into the cluster being inspected, which is why these two are documented together.

Agents Backend

In the APIHUB namespace

  • Agent registry

    Registration and heartbeats every ~30 s

  • Discovery orchestration

    Workspace-scoped runs against one agent

  • Snapshot service

    Packages found specs and publishes versions

  • Namespace security

    auth-check processes and Excel reports

  • Proxy

    Forwards UI calls through the agent

APIHUB Agent

In the target cluster

  • Registration

    Announces itself, then heartbeats

  • Cloud service

    client-go lists namespaces, services, routes

  • Document discovery

    Runs every runner in parallel per service

Six discovery runners — a plug-in point

REST GraphQL AsyncAPI Markdown JSON Schema Generic

Adding a seventh means one file implementing the runner interface, plus one line registering it.

L4

Code

Only the parts of the code you need to know to change something safely: the request pipeline, the two registries that the whole processing stack branches on, and the build orchestrator.

The backend request pipeline

Every request walks the same seven stages. Two side systems hang off it: the authentication strategies, tried in order, and the scheduled jobs.

  1. mux.Router

    Service.go

    ›
  2. Middleware

    Metrics, deadlines

    ›
  3. Security

    go-guardian union

    ›
  4. Controller

    39 of them

    ›
  5. Service

    Business logic

    ›
  6. Repository

    go-pg

    ›
  7. Entity

    Database rows

Authentication strategies · tried in order

  1. 1 Cookie JWT
  2. 2 Bearer JWT
  3. 3 Refresh token
  4. 4 API key
  5. 5 Personal token
  6. 6 Custom JWT
  7. 7 Ephemeral file token

All of them end in one security context, which the role service then reads.

Scheduled jobs · robfig/cron

  • Revisions 0 21 * * 0
  • Comparisons 0 5 * * 0
  • Soft-deleted data 0 22 * * 5
  • Unreferenced data 0 15 * * 6
  • Maintenance VACUUM 0 2 * * 1
  • Builds 0 1 * * 0
  • Ephemeral files */5 * * * *
  • AI chat retention 15 3 * * *

One type, two registries

Everything the processing stack does branches on a single union type. This is the extension point: add a member and the two registries tell you what still needs an implementation.

SpecType

api-unifier/src/spec-type.ts

openapi-3.0 openapi-3.1 json-schema-04 json-schema-06 json-schema-07 asyncapi-3 graphapi ddlapi-1.0

RULES

api-unifier

Normalization rules per type — resolve references, merge schemas, hash

COMPARE_ENGINES_MAP

api-diff

A comparison engine per type, producing classified changes

Adding a type touches eight places

  1. 1 spec-type.ts
  2. 2 RULES map
  3. 3 Compare engine
  4. 4 api-processor builder
  5. 5 Backend ApiType
  6. 6 UI api-types.ts
  7. 7 Consumer schema
  8. 8 Agent runner

DDLAPI went through this entire path during 2026, which is the best available worked example.

The build orchestrator

PackageVersionBuilder picks one strategy for the requested build type, and one builder per document. A third registry, keyed on the API type rather than the spec type, holds the builders.

Seven API builders · one per document kind

rest graphql asyncapi ddl mcp text unknown

parse › build document › build operations › compare › dump

Ten strategies · one per build type

  • build
  • changelog
  • prefix-groups-changelog
  • exportVersion
  • exportRestDocument
  • exportRestOperationsGroup
  • exportGraphqlOperationsGroup
  • exportAsyncapiOperationsGroup
  • documentGroup
  • mergedSpecification

What a build actually produces

Documents, operations, classified changes, backward-compatibility findings and a result index — packed into one ZIP that the backend unpacks and stores.

DEPLOY

What runs where

One umbrella Helm chart, one ingress, three databases — and a separate chart for the Java worker. Only the build consumer is designed to scale out, which keeps the Olric cluster and the migration framework simple.

Deployment view — helm-templates/qubership-apihub
APIHUB Kubernetes deployment: the ingress, six deployments, the data stores, remote-cluster agents, and the extras shipped in the Helm chart

APIHUB UI

1 replica

APIHUB Backend

1 · 3 CPU / 3 Gi

Build Task Consumer

3–6 replicas

API Linter Service

1 · 1 Gi

Agents Backend

1 · 256 Mi

Java Task Consumer

Separate chart

Data and caching

  • PostgreSQL 14+

    apihub, apihub_linter, agents

  • Olric

    Embedded in the backend and linter pods

  • S3 / MinIO

    Optional

Also in the chart

  • Prometheus ServiceMonitor
  • Two Grafana dashboards
  • Configuration and secrets per service
  • Quickstart: PostgreSQL, Keycloak, Superset
  • Compose variants for local runs