Self-hosted Go code coverage API and dashboard for ingesting test coverage, comparing deltas, and tracking trends across projects, branches, and teams.
coverage-api is part of the opencoverage project and is designed for developer teams that want coverage visibility in their own infrastructure.
- Ingest Go coverage results from local runs or CI pipelines
- Compute deterministic baseline comparisons and deltas
- Track project and package-level coverage history
- Group projects (for team-level reporting)
- Integrate with GitHub Actions and other CI systems
- Self-host API + frontend with PostgreSQL
- REST API with
/v1endpoints for ingest, history, and latest comparison - Coverage CLI to convert
coverage.outinto API-ready JSON payloads - Dashboard frontend for project, multi-branch trend, comparison, and heatmap visualization
- Integration test result ingestion and heatmap visualization
- API key authentication for protected endpoints
- Hexagonal Architecture (ports and adapters)
Run the full local stack (PostgreSQL + API + seed + frontend + MCP):
make compose-upIf local port 5432 is already in use:
DB_PORT=5433 make compose-upAccess services:
- API:
http://localhost:8080 - Frontend dashboard:
http://localhost:8090 - Health check:
http://localhost:8080/healthz
Compose startup behavior:
dbstarts first and must pass its healthcheck.apistarts next and runs all DB migrations during startup.apiexposes/healthzonly after startup completes.seedruns only afterapiis healthy.mcpstarts afterseedcompletes successfully.
Stop the stack:
make compose-downRequirements:
- Go 1.23+
- PostgreSQL 14+
Core environment variables:
DATABASE_URL(required)MIGRATIONS_DIR(default./migrations, used by API startup migrations)API_KEY_SECRET(required)SERVER_ADDR(default:8080)API_KEY_HEADER(defaultX-API-Key)SHUTDOWN_TIMEOUT_SECONDS(default10)
Run API locally:
export DATABASE_URL="postgres://coverage:coverage@localhost:5432/coverage?sslmode=disable"
export API_KEY_SECRET="dev-local-key"
go run ./cmd/apicmd/api runs migrations on startup before serving HTTP traffic.
Run MCP server locally:
export DATABASE_URL="postgres://coverage:coverage@localhost:5432/coverage?sslmode=disable"
export MCP_SERVER_NAME="opencoverage"
export MCP_SERVER_VERSION="dev"
go run ./cmd/mcpcmd/mcp does not run migrations. Start the API first (or run make migrate-up) before starting MCP against a fresh database.
Optional MCP settings:
MCP_TRANSPORT(defaultstdio)MCP_ENABLE_PROMPTS(defaulttrue)MCP_LOG_LEVEL(defaultinfo) - MCP server log verbosity (for example:debug,info,warn,error).MCP_ENABLE_WRITE_TOOLS(defaultfalse) - enablesingest_coverage_runandingest_integration_run; write tools require a transport that can carry request headers and a non-emptyAPI_KEY_SECRETin your shell or deployment environment (for exampleexport API_KEY_SECRET="dev-local-key") before startinggo run ./cmd/mcp.MCP_MAX_PAGE_SIZE(default100)MCP_DEFAULT_RUNS_LIMIT(default20)
Run frontend locally:
make frontend-runUseful developer commands:
make deps
make fmt
make test
make migrate-status
make migrate-upMain endpoints:
GET /v1/projectsPOST /v1/coverage-runsGET /v1/projects/{projectId}GET /v1/projects/{projectId}/coverage-runsGET /v1/projects/{projectId}/coverage-runs/latest-comparisonGET /v1/projects/{projectId}/branchesGET /v1/projects/{projectId}/contributorsPOST /v1/integration-test-runsGET /v1/integration-test-runs/heatmapGET /v1/projects/{projectId}/integration-test-runsGET /v1/projects/{projectId}/integration-test-runs/latest-comparisonGET /v1/projects/{projectId}/integration-test-runs/{runId}
For full API contract details, see SPEC.md.
Set variables:
export BASE_URL="http://localhost:8080"
export API_KEY="dev-local-key"Send a coverage run:
curl -i -X POST "$BASE_URL/v1/coverage-runs" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"projectKey": "org/repo-service",
"projectName": "repo-service",
"projectGroup": "platform-team",
"defaultBranch": "main",
"branch": "main",
"commitSha": "a1b2c3d4",
"author": "alice",
"triggerType": "push",
"runTimestamp": "2026-03-28T12:00:00Z",
"totalCoveragePercent": 83.42,
"packages": [
{"importPath": "github.com/acme/repo-service/internal/api", "coveragePercent": 85.10},
{"importPath": "github.com/acme/repo-service/internal/service", "coveragePercent": 80.70}
]
}'Generate payload from Go coverage profile:
go run ./cmd/coveragecli \
-coverprofile coverage.out \
-out coverage-upload.json \
-project-key "github.com/example/repo" \
-project-name "repo" \
-project-group "platform" \
-default-branch "main" \
-branch "main" \
-commit-sha "abc123" \
-author "alice"Generate and upload in one command:
make coverage-upload API_URL="http://localhost:8080/v1/coverage-runs" API_KEY="dev-local-key"Install CLI from GitHub:
go install github.com/arxdsilva/opencoverage/cmd/coveragecli@latestGenerate Vitest coverage summary:
npx vitest run --coverage --coverage.reporter=lcov --coverage.reporter=json-summaryUpload coverage/coverage-summary.json using the CLI:
go run ./cmd/coveragecli npm-upload \
-vitest-summary coverage/coverage-summary.json \
-api-url http://localhost:8080/v1/coverage-runs \
-api-key dev-local-key \
-project-key github.com/example/frontend-repo \
-project-name frontend-repo \
-project-group frontend \
-default-branch main \
-branch main \
-commit-sha abc123 \
-author alice \
-trigger-type pushUseful options:
-metric lines|statements|functions|branches(defaultlines)-group-by dir|file(defaultdir)-include-globand-exclude-glob(repeatable)-path-strip-prefixfor deterministic path normalization-dry-runand-out <payload.json>to inspect payload without upload
This project follows Hexagonal Architecture (ports and adapters):
cmd/api- bootstrap and dependency wiringinternal/domain- entities, invariants, deterministic domain logicinternal/application- use cases and portsinternal/adapters/http- handlers, DTOs, middlewareinternal/adapters/postgres- repository implementationsinternal/adapters/auth- API key authentication adapterinternal/platform- config and infrastructure utilities
Migration files are in migrations/.
Migration ownership:
- API process owns automatic migration execution at startup.
- MCP and frontend processes never run migrations.
- In Docker Compose,
seedwaits for API health so seeding happens after migrations.
Common commands:
make migrate-status
make migrate-up
make migrate-down
make migrate-create name=add_new_tableSeed local database:
make seedFor complete CI examples (unit tests, coverage.out, CLI payload generation, upload to self-hosted API), see GITHUB_ACTIONS_INTEGRATION.md.
The examples include:
- Configurable project metadata (
project key,name,group) - Push and pull request workflows
- Multi-project matrix workflows for monorepos
- PR comments and threshold-based quality gates
- Frontend behavior and UI notes: frontend.md
- API contract and response model: SPEC.md
- Contribution/PR workflow: making-a-PR.md
Frontend highlights:
- Project overview includes a multi-branch coverage trend chart.
- The trend view overlays the default branch with all discovered branches for the selected project.
- The branch selector is used for latest-comparison details, not for filtering the trend chart.
- Heatmap overlay shows all projects grouped by team, with tiles color-coded on a -3% to +3% delta scale (green = improved, red = regressed). A scale legend is displayed in the overlay header.
- Top Contributors overlay shows the leading commit contributors per project across all teams, grouped the same way as the heatmap.
- Integration Tests screen provides per-project integration test run history and failed spec details.
- Integration Run Chain is rendered oldest to newest (newest on the right) and shows up to 5 runs.
- Integration Pass Rate card shows run success ratio percentage computed as
passed runs / failed runs * 100over the returned run-list window (up to last 20 runs). - Integration Heatmap shows all projects grouped by team but displays default-branch runs only, with
✅/❌run markers and newest-run status tint per project row. - Integration Tests sidebar now supports group-first project navigation: filter by project group, then select a project from the filtered list (combined with project search).
- Run tests and produce
coverage.out. - Convert coverage profile to JSON payload with
coveragecli. - Upload payload to
POST /v1/coverage-runs. - Read comparison metadata (
thresholdStatus,deltaPercent) in CI. - Visualize trends and project groups in the dashboard.



