Quality Gate API
All endpoints are tenant-scoped via session / API key. Org admins (ORG_OWNER, ORG_ADMIN) or SUPER_ADMIN only for PUT/POST to /quality/gates/config, waivers, and baseline rebuild.
Base URL: https://codestax.co/api (or your self-hosted equivalent).
Auth: same JWT / API key as the rest of the platform — send Cookie (dashboard) or Authorization: Bearer <JWT> (CI/CD).
Quality Gate Config
GET /quality/gates/config
Returns the current org’s policy. Missing row returns defaults with configured: false.
GET /api/quality/gates/config
Cookie: ...
HTTP/1.1 200 OK
{
"org_id": 42,
"enabled": true,
"block_merge_on_fail": false,
"applies_to_new_code_only": true,
"policy": {
"new_critical_max": 0,
"new_high_max": 2,
"duplication_pct_max": 3.0,
"complexity_new_max": 15,
"rating_min": "C",
"new_coverage_min": 80.0
},
"policy_version": "v1",
"configured": true
}PUT /quality/gates/config
Admin only. Upsert the org’s policy.
PUT /api/quality/gates/config
Content-Type: application/json
{
"policy": {
"new_critical_max": 0,
"new_high_max": 0,
"new_coverage_min": 85,
"rating_min": "B"
},
"enabled": true,
"block_merge_on_fail": false,
"applies_to_new_code_only": true
}
HTTP/1.1 200 OK
{ "org_id": 42, "saved": true, "policy": { ... } }Unknown policy keys are dropped; unset keys fall back to defaults.
Gate Status Per Scan
GET /quality/gates/status/:scan_id
Returns the precomputed gate status (set by the scanner at scan-completion).
GET /api/quality/gates/status/12345
HTTP/1.1 200 OK
{
"scan_id": 12345,
"status": "failed",
"violations": [
{ "rule": "new_critical_max", "threshold": 0, "actual": 2, "severity": "critical" },
{ "rule": "new_coverage_min", "threshold": 80, "actual": 72.4, "severity": "medium" }
],
"default_branch": "main",
"scanned_branch": "feature/auth-rework",
"is_baseline_scan": false,
"new_in_pr": { "critical": 2, "high": 1, "medium": 4, "low": 0, "total": 7 }
}status ∈ passed | failed | not_configured.
POST /quality/gates/evaluate/:scan_id
Re-evaluate findings against the current org policy without writing. Useful when you change policy and want to preview the impact on historical scans.
POST /api/quality/gates/evaluate/12345
HTTP/1.1 200 OK
{
"status": "passed",
"violations": [],
"waived_violations": [
{ "rule": "duplication_pct_max", "threshold": 3, "actual": 4.2, "severity": "high", "waived": true }
],
"policy_version": "v1",
"policy": { ... }
}Waivers
GET /quality/gates/waivers
List waivers for the current org. Query params: include_expired=false, include_revoked=false, repo_id=<int>.
GET /api/quality/gates/waivers?include_expired=true
HTTP/1.1 200 OK
{
"org_id": 42,
"waivers": [
{
"id": 7,
"rule": "duplication_pct_max",
"reason": "Legacy monolith — scheduled refactor ticket #1234",
"repo_id": 8,
"pr_number": null,
"created_by_user_id": 15,
"created_at": "2026-04-15T10:00:00Z",
"expires_at": "2026-06-01T00:00:00Z",
"revoked_at": null,
"expired": false,
"revoked": false
}
]
}POST /quality/gates/waivers
Admin only. Create a waiver.
POST /api/quality/gates/waivers
Content-Type: application/json
{
"rule": "new_critical_max",
"reason": "CVE-2026-XXXX is in a dev-only test harness, not deployed",
"repo_id": 8,
"pr_number": 142,
"expires_at": "2026-05-01T00:00:00Z"
}
HTTP/1.1 200 OK
{
"id": 8,
"rule": "new_critical_max",
"repo_id": 8,
"pr_number": 142,
"created_at": "2026-04-19T12:00:00Z",
"expires_at": "2026-05-01T00:00:00Z"
}Scope ladder: specific PR → specific repo → org-wide. Most-specific match wins during evaluation.
POST /quality/gates/waivers/:id/revoke
Admin only. Soft-delete a waiver — stops honoring it on the next scan.
POST /api/quality/gates/waivers/8/revoke
HTTP/1.1 200 OK
{ "id": 8, "revoked": true, "revoked_at": "2026-04-19T14:00:00Z" }Baseline Rebuild
POST /quality/gates/rebuild-baseline/:repo_id
Admin only. Clears the default-branch QualityBaseline fingerprints for this repo. Next default-branch scan repopulates the baseline. Use after a big refactor.
POST /api/quality/gates/rebuild-baseline/8
HTTP/1.1 200 OK
{
"repo_id": 8,
"branch": "main",
"cleared_fingerprints": 847,
"note": "Next scan on the default branch will re-establish the baseline."
}Quality Insights
GET /quality/ratings/:scan_id
GET /api/quality/ratings/12345
HTTP/1.1 200 OK
{
"scan_id": 12345,
"quality_rating": "B",
"maintainability_rating": "B",
"reliability_rating": "A",
"coverage_rating": "C",
"duplication_rating": "A",
"tech_debt_minutes": 847,
"parse_coverage_pct": 98.2,
"files_parsed": 1847,
"files_skipped": 34,
"tool_versions": { "lizard": "1.17.13", "semgrep": "1.95.0", ... }
}GET /quality/tech-debt/:scan_id
GET /api/quality/tech-debt/12345
HTTP/1.1 200 OK
{
"scan_id": 12345,
"total_minutes": 847,
"total_hours": 14.12,
"total_engineer_days": 1.76,
"findings_by_type": {
"complexity": 23,
"dead_code": 8,
"duplication": 5,
"coverage_gap": 12
}
}Coverage
POST /quality/coverage/upload
Upload a coverage report. Multipart form: scan_id (required, int), report_format (optional — lcov / cobertura / jacoco / clover, auto-detected if absent), file (required). Max 25 MB.
curl -X POST https://codestax.co/api/quality/coverage/upload \
-H "Authorization: Bearer $JWT" \
-F "scan_id=12345" \
-F "report_format=lcov" \
-F "file=@coverage/lcov.info"{ "scan_id": 12345, "records_persisted": 847 }GET /quality/coverage/:scan_id
GET /api/quality/coverage/12345
HTTP/1.1 200 OK
{
"scan_id": 12345,
"file_count": 847,
"lines_total": 42100,
"lines_covered": 38700,
"coverage_pct": 91.92,
"files": [
{
"file_path": "src/auth/handler.py",
"lines_total": 420,
"lines_covered": 412,
"lines_missed": 8,
"coverage_pct": 98.1,
"report_format": "lcov"
}
]
}Mark False Positive
POST /quality/findings/mark-fp
Tenant-scoped (verified against scan → repo → org chain).
POST /api/quality/findings/mark-fp
Content-Type: application/json
{ "issue_id": 98765, "is_fp": true, "reason": "Sanitized via framework encoder at the boundary" }
HTTP/1.1 200 OK
{ "issue_id": 98765, "user_marked_fp": true, "fp_marked_at": "2026-04-19T15:00:00Z", "fp_reason": "..." }Send is_fp: false to unmark.
CI/CD Usage
Use /quality/gates/status/:scan_id in your pipeline to block deployment on gate failure. Example (GitHub Actions):
- name: Wait for CodeStax scan + check gate
run: |
SCAN_ID="${{ steps.trigger.outputs.scan_id }}"
until [ "$(curl -sf -H "Authorization: Bearer $CODESTAX_JWT" \
"https://codestax.co/api/quality/gates/status/$SCAN_ID" | jq -r .status)" != "null" ]; do
sleep 10
done
STATUS=$(curl -sf -H "Authorization: Bearer $CODESTAX_JWT" \
"https://codestax.co/api/quality/gates/status/$SCAN_ID" | jq -r .status)
echo "Gate: $STATUS"
[ "$STATUS" = "passed" ] || [ "$STATUS" = "not_configured" ] || exit 1Error Responses
| Status | When |
|---|---|
401 | No session / invalid JWT / revoked API key |
403 | Not an org admin for config / waivers / rebuild-baseline; non-super-admin crossing tenants |
404 | Scan / waiver / repo not found in caller’s org |
400 | Policy JSON malformed; coverage upload > 25 MB; unparseable format |
429 | Rate-limited (per-route; login routes have stricter caps) |