Skip to content

CI/CD Integration

Integrating VisibilityIQ into your CI/CD pipeline lets you catch SEO regressions automatically — before a bad deploy makes it to production.

Deploy to staging
Trigger VisibilityIQ audit on staging domain
Poll (or webhook) until audit completes
Fetch new Critical + High findings
Compare against previous audit baseline
┌───┴────┐
│ │
New Critical No new Critical
findings? findings?
│ │
▼ ▼
Fail CI ✗ Pass CI ✓
name: SEO Audit
on:
deployment_status:
# Run after a successful deploy to staging
jobs:
seo-audit:
if: github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
steps:
- name: Trigger VisibilityIQ audit
id: trigger
run: |
RESPONSE=$(curl -s -X POST \
"https://visibilityiq365.com/api/v1/projects/${{ vars.VIQ_PROJECT_ID }}/audits" \
-H "Authorization: Bearer ${{ secrets.VIQ_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"crawlDepth": 2, "renderMode": "auto"}')
AUDIT_ID=$(echo $RESPONSE | jq -r '.data.auditId')
echo "audit_id=$AUDIT_ID" >> $GITHUB_OUTPUT
- name: Wait for audit to complete
run: |
AUDIT_ID="${{ steps.trigger.outputs.audit_id }}"
MAX_WAIT=1800 # 30 minutes
ELAPSED=0
while [ $ELAPSED -lt $MAX_WAIT ]; do
STATUS=$(curl -s \
"https://visibilityiq365.com/api/v1/projects/${{ vars.VIQ_PROJECT_ID }}/audits/$AUDIT_ID" \
-H "Authorization: Bearer ${{ secrets.VIQ_API_KEY }}" \
| jq -r '.data.status')
echo "Audit status: $STATUS (${ELAPSED}s elapsed)"
if [ "$STATUS" = "completed" ]; then
break
elif [ "$STATUS" = "failed" ]; then
echo "Audit failed"
exit 1
fi
sleep 30
ELAPSED=$((ELAPSED + 30))
done
- name: Check for new Critical findings
run: |
AUDIT_ID="${{ steps.trigger.outputs.audit_id }}"
NEW_CRITICAL=$(curl -s \
"https://visibilityiq365.com/api/v1/projects/${{ vars.VIQ_PROJECT_ID }}/issues?severity=critical&status=new&auditId=$AUDIT_ID" \
-H "Authorization: Bearer ${{ secrets.VIQ_API_KEY }}" \
| jq '.data.total')
echo "New Critical findings: $NEW_CRITICAL"
if [ "$NEW_CRITICAL" -gt "0" ]; then
echo "❌ $NEW_CRITICAL new Critical SEO finding(s) detected"
# Print finding details
curl -s \
"https://visibilityiq365.com/api/v1/projects/${{ vars.VIQ_PROJECT_ID }}/issues?severity=critical&status=new&auditId=$AUDIT_ID" \
-H "Authorization: Bearer ${{ secrets.VIQ_API_KEY }}" \
| jq -r '.data.findings[] | "• \(.checkId) on \(.pageUrl): \(.guidance.summary)"'
exit 1
fi
echo "✓ No new Critical findings"

In your GitHub repository settings:

  • Secret VIQ_API_KEY — your VisibilityIQ API key with audit:read and projects:read scopes
  • Variable VIQ_PROJECT_ID — your project ID (found in Project → Settings)

The status=new filter returns only findings that weren’t present in the previous audit. This is the key filter for CI integration — you don’t want to fail CI because of pre-existing issues that predate your deployment.

If you want to fail on all Critical findings (not just new ones), remove the status=new filter.

If your staging environment uses a different domain than production, create a separate VisibilityIQ project for the staging domain. The API key and project ID in your CI secrets would point to the staging project.

For continuous monitoring of production, use webhooks (audit.completed events with new Critical findings) rather than CI-triggered polls — production audits typically run on a schedule, not per-deploy.

Add a step to post findings to Slack when CI fails:

- name: Notify Slack on SEO regression
if: failure()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
-H 'Content-type: application/json' \
-d '{
"text": "🚨 SEO regression detected in deploy",
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "New Critical SEO findings after latest deploy to staging. <https://visibilityiq365.com/projects/${{ vars.VIQ_PROJECT_ID }}|View audit>"
}
}]
}'