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.
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.
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
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.
The seven containers in detail
- 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
- 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
- Technology
- Node 24 · NestJS 10
- State
- Stateless — scale to 3–6
- Owns
- Runs PackageVersionBuilder: parse, normalize, diff, changelog, export
- 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
- Technology
- Go 1.26 · client-go
- State
- Stateless
- Owns
- Six discovery runners in parallel per service, plus a proxy into the cluster
- Technology
- Java 21 · japicmp · jdeps
- State
- Stateless
- Owns
- japicmp and jdeps pipelines; the backend java-builders queue is specified only
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
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
Task poller
An RxJS interval asks /api/v2/builders/{id}/tasks
- 2
Schema validation
ajv over the build configuration
- 3
PackageVersionBuilder
api-processor: 7 builders, 10 strategies
- 4
Resolvers
Fetch previous versions from the backend
- 5
Result sender
ZIP to /api/v3/…/publish/{id}/status
Live and scaled in production
Java Task Consumer
Pendingqubership-apihub-java-task-consumer
- 1
Task poller
Asks /api/v2/java-builders/{id}/tasks
- 2
Build executor
Maps a build type to a pipeline
- 3
jdiff-engine
ApiReport · ApiDiff · UpgradeImpact
- 4
Jar resolver
Maven coordinates or OCI image layers
- 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.
-
01
Publish event
Olric topic version-published
-
02
Version task
Fetch documents from the backend
-
03
Document tasks
Fan out into a worker pool
-
04
Linter selector
Engine per API type and package
-
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
Adding a seventh means one file implementing the runner interface, plus one line registering it.
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.
- ›
mux.Router
Service.go
- ›
Middleware
Metrics, deadlines
- ›
Security
go-guardian union
- ›
Controller
39 of them
- ›
Service
Business logic
- ›
Repository
go-pg
-
Entity
Database rows
Authentication strategies · tried in order
- 1 Cookie JWT
- 2 Bearer JWT
- 3 Refresh token
- 4 API key
- 5 Personal token
- 6 Custom JWT
- 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
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 spec-type.ts
- 2 RULES map
- 3 Compare engine
- 4 api-processor builder
- 5 Backend ApiType
- 6 UI api-types.ts
- 7 Consumer schema
- 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
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.
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.
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