Developer Documentation
CI/CD Pipeline Integration
Automate HIPAA, GDPR, SOC 2, PCI DSS, DPDPA, and Singapore MGF compliance scanning in your CI/CD pipeline. Incremental PR scanning, pass/fail gating, and PR comments, all API-driven.
Quick Start
Get up and running in three steps.
Generate an API key
Go to Settings → API Keys and create a new key. Save it securely; you will only see it once.
Add key to your CI secrets
Store your API key as a secret in your CI/CD platform.
SECUREHEALTH_API_KEY=sk_live_your_key_here
- GitHub: Settings → Secrets → Actions → New secret
- GitLab: Settings → CI/CD → Variables (masked + protected)
- GitLab MR notes: Also add
GITLAB_API_TOKENwithapiscope
Add the scan step to your pipeline
Add a workflow step that zips your code, uploads it, and checks the result. See full examples below.
# 1. Zip your source code
zip -r codebase.zip . -x '.git/*' 'node_modules/*' 'venv/*'
# 2. Upload and start scan (with git context for incremental mode)
SCAN_ID=$(curl -s -X POST \
-H "Authorization: Bearer $SECUREHEALTH_API_KEY" \
-F "code=@codebase.zip" \
"https://api.scrutora.com/api/v1/scans?frameworks=hipaa&scan_mode=auto&branch=main&commit_sha=$(git rev-parse HEAD)" \
| jq -r '.scan_id')
# 3. Poll for results
while true; do
STATUS=$(curl -s \
-H "Authorization: Bearer $SECUREHEALTH_API_KEY" \
"https://api.scrutora.com/api/v1/scans/$SCAN_ID" \
| jq -r '.status')
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 10
done
# 4. Get exit code (0 = pass, 1 = fail)
EXIT=$(curl -s \
-H "Authorization: Bearer $SECUREHEALTH_API_KEY" \
"https://api.scrutora.com/api/v1/scans/$SCAN_ID/exit-code" \
| jq -r '.exit_code')
exit $EXITAPI Reference
All endpoints require a valid API key in the Authorization header.
Authorization: Bearer sk_live_.../api/v1/scansCreate a scan
Upload a zip archive and start a compliance scan. Supports multi-framework scanning, incremental mode, and git context for CI/CD.
Parameters
| Name | Type | Description |
|---|---|---|
| frameworks | query | Comma-separated: hipaa, gdpr, soc2. Default: hipaa |
| scan_mode | query | full, incremental, or auto. Default: full. Incremental requires Business+ AND a .git directory inside the uploaded archive: changed files are computed with git diff against base_branch. Without .git history the scan automatically falls back to a full scan. |
| commit_sha | query | Git commit SHA for incremental diff tracking |
| branch | query | Branch name (e.g. feature/auth) |
| pr_number | query | PR number: enables incremental mode with auto |
| base_branch | query | Target branch of the PR (e.g. main) |
| project_id | query | Associate scan with a project |
| project_name | query | Create or match project by name |
Request
curl -X POST \ "https://api.scrutora.com/api/v1/scans?frameworks=hipaa,gdpr&scan_mode=auto&commit_sha=a1b2c3d&branch=feature/auth&pr_number=42&base_branch=main" \ -H "Authorization: Bearer sk_live_..." \ -F "code=@codebase.zip"
Response
{
"scan_id": "a1b2c3d4e5f6...",
"status": "processing",
"message": "Scan queued successfully. Poll GET /api/v1/scans/{scan_id} for status."
}/api/v1/scans/{scan_id}Get scan status
Check the status of a scan and view basic results when complete.
Parameters
| Name | Type | Description |
|---|---|---|
| fail_on_critical | query | Consider critical violations a failure. Default: true |
| fail_on_high | query | Consider high violations a failure. Default: false |
Request
curl "https://api.scrutora.com/api/v1/scans/{scan_id}" \
-H "Authorization: Bearer sk_live_..."Response
{
"scan_id": "a1b2c3d4e5f6...",
"status": "completed",
"violations": { "critical": 2, "high": 5, "medium": 8, "low": 3, "total": 18 },
"passed": false,
"risk_score": 45,
"compliance_grade": "D",
"report_url": "https://api.scrutora.com/api/v1/scans/a1b2c3d4.../report"
}/api/v1/scans/{scan_id}/detailsGet detailed scan status (incremental)
Extended status with incremental diff data. Returns new/resolved findings compared to the previous full scan baseline. Use this endpoint for PR comments and detailed CI gating.
Parameters
| Name | Type | Description |
|---|---|---|
| fail_threshold | query | Severity threshold: critical, high, medium, low, none. Default: high |
Request
curl "https://api.scrutora.com/api/v1/scans/{scan_id}/details?fail_threshold=high" \
-H "Authorization: Bearer sk_live_..."Response
{
"scan_id": "a1b2c3d4e5f6...",
"status": "completed",
"scan_mode": "incremental",
"source": "cicd",
"commit_sha": "a1b2c3d",
"branch": "feature/auth",
"pr_number": 42,
"base_branch": "main",
"files_scanned": 5,
"files_total": 87,
"changed_files": ["src/auth.py", "src/utils.py"],
"score": 85.0,
"violations_count": 3,
"new_findings_count": 2,
"resolved_findings_count": 1,
"new_findings": [
{ "rule_id": "AUTH-001", "severity": "critical", "file": "src/auth.py", "line": 42, "message": "Missing access control" }
],
"resolved_findings": [
{ "rule_id": "TRANS-001", "severity": "high", "file": "src/utils.py", "line": 15, "message": "Fixed: TLS now enforced" }
],
"check_status": "fail",
"check_message": "2 new HIGH+ finding(s) in required frameworks",
"fail_threshold": "high",
"passed": false,
"report_url": "https://api.scrutora.com/api/v1/scans/a1b2c3d4.../report"
}/api/v1/scans/{scan_id}/exit-codeGet CI exit code
Returns 0 for pass, 1 for fail. Use this to gate deployments in your CI pipeline.
Parameters
| Name | Type | Description |
|---|---|---|
| fail_on_critical | query | Fail on critical violations. Default: true |
| fail_on_high | query | Fail on high-severity violations. Default: false |
| min_score | query | Minimum passing score (0-100). Default: 0 |
Request
curl "https://api.scrutora.com/api/v1/scans/{scan_id}/exit-code" \
-H "Authorization: Bearer sk_live_..."Response
{
"exit_code": 1,
"passed": false,
"reason": "2 critical violation(s)"
}/api/v1/scans/{scan_id}/reportDownload PDF report
Download the full compliance report as a PDF. Supports HIPAA, GDPR, SOC 2, DPDPA, Singapore MGF, and PCI DSS frameworks.
Request
curl -OJ "https://api.scrutora.com/api/v1/scans/{scan_id}/report" \
-H "Authorization: Bearer sk_live_..."Response
# Returns PDF binary file # Content-Type: application/pdf
/api/v1/scans/{scan_id}/sarifDownload SARIF results
Download scan results in SARIF v2.1.0 format. Compatible with GitHub Code Scanning, Azure DevOps, VS Code SARIF Viewer, and other static analysis tools.
Request
curl -OJ "https://api.scrutora.com/api/v1/scans/{scan_id}/sarif" \
-H "Authorization: Bearer sk_live_..."Response
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/...",
"version": "2.1.0",
"runs": [{
"tool": { "driver": { "name": "Scrutora", "rules": [...] } },
"results": [
{
"ruleId": "AUTH-001",
"level": "error",
"message": { "text": "Missing access control check" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "src/auth.py" } } }]
}
]
}]
}CI/CD Examples
Copy-paste configurations for popular CI/CD platforms.
Incremental scan on every pull request. Posts a PR comment with results and fails the check if violations exceed the threshold.
name: Scrutora PR Scan
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
compliance-scan:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
env:
SECUREHEALTH_API_URL: https://api.scrutora.com
FRAMEWORKS: hipaa
FAIL_THRESHOLD: high
POLL_TIMEOUT: 600
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Create archive
run: zip -r codebase.zip . -x '.git/*' 'node_modules/*' 'venv/*' '__pycache__/*' '.next/*' 'dist/*' 'build/*'
- name: Upload and scan
id: scan
run: |
PARAMS="frameworks=$FRAMEWORKS&scan_mode=auto&commit_sha=${{ github.sha }}&branch=${{ github.head_ref }}"
PARAMS="$PARAMS&pr_number=${{ github.event.pull_request.number }}&base_branch=${{ github.base_ref }}"
RESPONSE=$(curl -s -X POST \
-H "Authorization: Bearer ${{ secrets.SECUREHEALTH_API_KEY }}" \
-F "code=@codebase.zip" \
"$SECUREHEALTH_API_URL/api/v1/scans?$PARAMS")
SCAN_ID=$(echo "$RESPONSE" | jq -r '.scan_id')
echo "scan_id=$SCAN_ID" >> "$GITHUB_OUTPUT"
echo "Scan started: $SCAN_ID"
- name: Wait for completion
id: result
run: |
SCAN_ID=${{ steps.scan.outputs.scan_id }}
TIMEOUT=$POLL_TIMEOUT
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
DETAILS=$(curl -s \
-H "Authorization: Bearer ${{ secrets.SECUREHEALTH_API_KEY }}" \
"$SECUREHEALTH_API_URL/api/v1/scans/$SCAN_ID/details?fail_threshold=$FAIL_THRESHOLD")
STATUS=$(echo "$DETAILS" | jq -r '.status')
if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
echo "details=$DETAILS" >> "$GITHUB_OUTPUT"
echo "$DETAILS" | jq .
break
fi
echo "Scanning... ($STATUS) — ${ELAPSED}s elapsed"
sleep 15
ELAPSED=$((ELAPSED + 15))
done
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const details = JSON.parse('${{ steps.result.outputs.details }}');
const mode = details.scan_mode === 'incremental' ? 'Incremental' : 'Full';
const status = details.check_status === 'pass' ? '✅ Pass' :
details.check_status === 'fail' ? '❌ Fail' : '⚠️ Warning';
let body = `## Scrutora Compliance Scan\n\n`;
body += `| Metric | Value |\n|---|---|\n`;
body += `| Score | ${details.score ?? 'N/A'} |\n`;
body += `| Violations | ${details.violations_count ?? 0} |\n`;
body += `| Scan Mode | ${mode} |\n`;
body += `| Status | ${status} |\n`;
if (details.new_findings_count > 0) {
body += `\n### New Findings (${details.new_findings_count})\n`;
for (const f of details.new_findings || []) {
body += `- **${f.severity.toUpperCase()}** \`${f.rule_id}\` ${f.file}:${f.line} — ${f.message}\n`;
}
}
if (details.resolved_findings_count > 0) {
body += `\n### Resolved (${details.resolved_findings_count}) 🎉\n`;
}
body += `\n[View Full Report](${details.report_url})\n`;
body += `---\n`;
body += `Scanned by [Scrutora](https://scrutora.com) | Commit: ${context.sha.substring(0, 7)}`;
// Update existing comment or create new one
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.data.find(c => c.body?.includes('Scrutora Compliance Scan'));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
- name: Check result
run: |
CHECK=$(echo '${{ steps.result.outputs.details }}' | jq -r '.check_status')
if [ "$CHECK" = "fail" ]; then
echo "::error::Compliance check failed"
exit 1
fiConfiguration Variables
| Variable | Default | Description |
|---|---|---|
| FRAMEWORKS | hipaa | Comma-separated: hipaa, gdpr, soc2 |
| FAIL_THRESHOLD | high | Severity gate: critical, high, medium, low, none |
| POLL_TIMEOUT | 600 | Max wait time in seconds. Increase for large repos. |
| SECUREHEALTH_API_URL | https://api.scrutora.com | API base URL. Override for self-hosted deployments. |
GitLab MR notes: To post scan results as MR notes, add a GITLAB_API_TOKEN CI/CD variable with a project access token that has api scope. Go to Settings → CI/CD → Variables and mark it as masked + protected.
PR Comment Examples
Both GitHub and GitLab workflows post scan results directly on your PR/MR. Here's what the comments look like.
Passing scan
✅ Scrutora Compliance Scan
| Score | 92 |
| Violations | 3 |
| Scan Mode | incremental |
| Check Status | pass |
All findings are below the configured severity threshold (high).
Failing scan
❌ Scrutora Compliance Scan
| Score | 45 |
| Violations | 12 |
| Scan Mode | incremental |
| Check Status | fail |
Incremental Diff
- New findings: 4
- Resolved findings: 1
4 new findings at HIGH severity or above. Fix these before merging.
Repo Configuration
Add a .scrutora.yml file to your repo root to configure scanning behavior without modifying API calls.
# Compliance frameworks to scan
frameworks:
- hipaa
- gdpr
# Scan mode for PR builds (full or incremental)
pr_scan_mode: incremental
# Scan mode for pushes to main (full or incremental)
merge_scan_mode: full
# Severity threshold for PR failure (critical, high, medium, low, none)
fail_threshold: high
# Only fail on findings from these frameworks
required_frameworks:
- hipaa
# ── Per-rule overrides ──────────────────────────────
rules:
# Disable a rule entirely
CONSENT-001:
enabled: false
# Override severity (critical, high, medium, low)
TRANSFER-001:
severity: low
# Set minimum confidence threshold (violations below are dropped)
RETENTION-001:
min_confidence: 0.60
# Combined overrides
PII-001:
severity: medium
min_confidence: 0.70
# ── Global settings ─────────────────────────────────
settings:
# Minimum confidence for any violation to be reported (0.0–1.0)
min_confidence: 0.40
# Glob patterns to exclude from scanning
exclude_paths:
- "tests/**"
- "docs/**"
- "migrations/**"
- "src/generated/**"
- "vendor/**"Configuration Priority
Settings are resolved in this order (first match wins):
- 1. API query parameters (explicit request)
- 2.
.scrutora.ymlin the repo - 3. Project-level defaults (set in dashboard)
- 4. Plan-based defaults (free = hipaa only)
Rule Override Options
Each rule in the rules: section supports:
enabled: Set tofalseto disable the ruleseverity: Override: critical, high, medium, or lowmin_confidence: Drop violations below this threshold (0.0–1.0)
Suppression File
Add a .scrutoraignore file to your repo root to suppress known false positives or accepted risks. Suppressed violations are still tracked but excluded from the report and CI gating.
# Suppress a rule everywhere rule:CONSENT-001 # Suppress all violations in a specific file file:src/generated/models.py # Suppress a rule only in a specific file rule:PII-001 file:app/legacy_service.py # Suppress a specific line rule:SDLC-001 file:config.py line:42 # Glob patterns work for file matching file:vendor/** file:*.generated.cs
Suppression Directives
Each line can contain one or more directives. Lines starting with # are comments.
rule:RULE-ID: Match by rule ID (e.g. AUTH-001, PII-001)file:path/to/file.py: Match by file path (supports glob patterns)line:42: Match by line number (must be combined with file:)
When multiple directives appear on one line, all must match for the violation to be suppressed.
GitHub PR Bot
BusinessAutomatically scan pull requests and post compliance results as PR comments. The PR Bot triggers on every PR opened, synchronized, or reopened, giving your team instant compliance feedback.
Webhook Setup
- 1. Go to your GitHub repo → Settings → Webhooks
- 2. Add webhook URL:
https://api.scrutora.com/api/github/pr/webhook - 3. Content type:
application/json - 4. Select events: Pull requests
- 5. Add your webhook secret (optional but recommended)
What Gets Posted
- • Score badge: pass / warning / fail with score
- • Severity breakdown: critical, high, medium, low counts
- • Top findings: up to 5 most important violations
- • File locations: exact files and line numbers
- • Framework coverage: HIPAA, GDPR, SOC 2, PCI DSS, DPDPA, and/or Singapore MGF
# Post scan results to an existing PR
curl -X POST "https://api.scrutora.com/api/github/pr/comment" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"scan_id": "abc123",
"repo_full_name": "your-org/your-repo",
"pr_number": 42
}'Prerequisite: Connect your GitHub account at Dashboard → Scan and ensure you're on a Business or Enterprise plan.
SARIF Export
Export scan results in SARIF v2.1.0 (Static Analysis Results Interchange Format) for integration with GitHub Code Scanning, Azure DevOps, VS Code SARIF Viewer, and other CI/CD tools.
# Download SARIF results after a scan completes
curl -OJ "https://api.scrutora.com/api/v1/scans/{scan_id}/sarif" \
-H "Authorization: Bearer sk_live_..."
# Upload to GitHub Code Scanning (in GitHub Actions)
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarifGitHub Code Scanning
Upload SARIF to see compliance violations as code scanning alerts directly in your GitHub repository.
Azure DevOps
Import SARIF results into Azure DevOps for centralized security dashboard and build gating.
VS Code
Open SARIF files in the VS Code SARIF Viewer extension to navigate violations inline in your editor.
Incremental Scanning
On Business and Enterprise plans, PR scans only analyze changed files and their dependencies, cutting scan time significantly for large codebases.
How It Works
- 1. Git diff detects files changed between your PR branch and base branch
- 2. Dependency closure expands to include files that import the changed files (1 level)
- 3. Only the closure files are scanned
- 4. Results are merged with the latest full scan baseline
- 5. New and resolved findings are computed as a diff
Force Full Scan Conditions
The scanner automatically upgrades to a full scan when:
- • No previous full scan baseline exists
- •
.scrutora.ymlwas modified in the PR - • More than 30% of files changed
- • Last full scan is older than 30 days
- • Push to main/master (always full)
- • Explicitly requested via
scan_mode=full
Plan requirement: Incremental scanning is available on Business and Enterprise plans. On Team/Free plans, scan_mode=auto silently falls back to a full scan.
Trend Analysis & Team Collaboration
BusinessTrack compliance scores over time, compare scans, assign findings to team members, and monitor portfolio-level metrics via the API.
Project Trends
Track score and violation counts for each project over 7–365 days. See if your compliance posture is improving, declining, or stable.
GET /api/trends/project/{id}Scan Comparison
Compare any two scans side-by-side: see new findings, resolved findings, and score/violation deltas.
GET /api/trends/compare?base_scan_id=...&head_scan_id=...Portfolio Overview
View overall compliance health across all projects. Identify projects needing attention with declining scores.
GET /api/trends/portfolioFinding Assignment & Tracking
Assign individual findings to team members and track their status through resolution. Available on Business and Enterprise plans.
# List findings for a project (filter by severity, rule, status, assignee)
curl "https://api.scrutora.com/api/findings?project_id=PROJ_ID&severity=CRITICAL" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Assign a finding to a team member
curl -X PUT "https://api.scrutora.com/api/findings/FINDING_ID/assign" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id": "TEAM_MEMBER_ID"}'
# Update finding status (open, in_progress, resolved, wont_fix, false_positive)
curl -X PUT "https://api.scrutora.com/api/findings/FINDING_ID/status" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "in_progress"}'
# Get aggregate statistics
curl "https://api.scrutora.com/api/findings/summary?project_id=PROJ_ID" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Best Practices
Store keys as secrets
Never hardcode API keys in your code. Use your CI system's secret management (GitHub Secrets, GitLab Variables, etc.).
Use auto scan mode
Set scan_mode=auto to get incremental scans on PRs and full scans on pushes to main. Incremental diffs against base_branch with git, so the uploaded archive must include its .git history (the base_branch ref + the commit). If .git is missing, the scan falls back to a full scan. Configure via .scrutora.yml or API params.
Gate deployments on /details
Use the /details endpoint with fail_threshold to get granular pass/fail based on new findings only, not the entire codebase.
Add .scrutora.yml to your repo
Configure frameworks, scan modes, and fail thresholds in your repo. No need to change CI scripts when settings change.
Run full scans on main
Always run full scans when merging to main. This refreshes the baseline for future incremental PR scans.
Exclude non-code files
Zip only source code. Exclude node_modules, venv, build artifacts, and binary files to reduce scan time. Keep .git when you use incremental/PR scans; it's required to compute the diff against base_branch; exclude it only for full scans. Use exclude_paths in .scrutora.yml to skip generated code.
Rotate API keys every 90 days
Create new API keys regularly and revoke old ones. Use separate keys per environment (staging vs production).
Use .scrutoraignore for false positives
Add a .scrutoraignore file to suppress known false positives or accepted risks instead of disabling entire rules. Target specific files and lines.
Export SARIF for code scanning
Download SARIF results and upload to GitHub Code Scanning or Azure DevOps for inline violation annotations in your PRs.
Run a full scan before going incremental
Incremental scanning compares against a baseline. Run a full scan on your main branch first so the scanner has a reference point, otherwise incremental results will be unreliable.
Ready to automate compliance scanning in your pipeline?
Generate your API key, add a .scrutora.yml to your repo, and get compliance scanning on every PR in minutes.