How do I add a website check to my CI pipeline?
Blocking a deploy when it would break your headers, certificate, indexability or accessibility — with copy-paste config for GitHub Actions and GitLab.
Call the API after deploy, assert on what you care about, and exit non-zero when it fails. Assert on specific findings rather than on the total score — a score threshold is noisy and blocks builds for reasons nobody will act on.
Assert on findings, not on the score
The instinct is to gate on a number: fail if the score drops below 85. In practice this produces a build that breaks for content changes and passes while something genuinely important regresses, because a single critical failure can be masked by improvements elsewhere.
Gate on the things that must never be true instead. They are stable, they never fire spuriously, and every one of them is worth stopping a deploy for.
- Any
noindexon a page that should be indexed. - A
robots.txtthat blocks a crawler you rely on. - A missing security header you had previously set.
- A certificate expiring inside your renewal window.
- A canonical pointing at a host that is not production.
- A new critical-severity finding of any kind.
GitHub Actions
name: Post-deploy site check
on:
workflow_run:
workflows: ["Deploy"]
types: [completed]
jobs:
inspect:
runs-on: ubuntu-latest
steps:
- name: Measure the deployed site
run: |
set -euo pipefail
result=$(curl -sf "https://outrings.com/api/v1/summary?url=${{ vars.SITE_URL }}&refresh=1")
# Anything critical is a hard stop.
critical=$(jq -r '[.priorityActions[] | select(.severity == "critical")] | length' <<<"$result")
if [ "$critical" -gt 0 ]; then
jq -r '.priorityActions[] | select(.severity == "critical") | "::error::\(.title) — \(.action)"' <<<"$result"
exit 1
fi
# Indexability is the expensive silent failure.
jq -e '.categories[] | select(.id == "discoverability") | .score > 70' <<<"$result" > /dev/null \
|| { echo "::error::Discoverability regressed — check robots.txt and noindex"; exit 1; }
echo "::notice::Site scored $(jq -r '.score' <<<"$result")"GitLab CI
site-check:
stage: verify
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
set -euo pipefail
result=$(curl -sf "https://outrings.com/api/v1/summary?url=$SITE_URL&refresh=1")
critical=$(jq -r '[.priorityActions[] | select(.severity == "critical")] | length' <<<"$result")
if [ "$critical" -gt 0 ]; then
jq -r '.priorityActions[] | select(.severity == "critical") | "CRITICAL: \(.title)"' <<<"$result"
exit 1
fi
echo "Score: $(jq -r '.score' <<<"$result")"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHComparing against the previous deploy
Absolute assertions catch known-bad states. To catch anything that got worse, compare instead:
result=$(curl -sf "https://outrings.com/api/v1/changes?url=$SITE_URL")
if [ "$(jq -r '.comparable' <<<"$result")" = "true" ]; then
# newProblems entries carry an id and a status of fail or warn.
# Gate on outright failures; warnings are worth reading, not blocking on.
new=$(jq -r '[.newProblems[]? | select(.status == "fail") | .id] | join("; ")' <<<"$result")
[ -z "$new" ] || { echo "Regression introduced: $new" >&2; exit 1; }
fi&refresh=1 in CI. Results are cached for ten minutes, and without it a check running immediately after deploy can happily measure the previous version of your site and pass.Practical notes
- Run it after deploy, against the real URL. Auditing a preview host tells you about the preview host — different headers, different certificate, often different robots rules.
- Make the first runs non-blocking. Use
continue-on-errorfor a week and read the output before you let it fail builds. You will find pre-existing findings you did not know about. - Do not gate on flaky signals. Latency-derived checks vary with network conditions; assert on configuration, which does not.
- Mind the rate limit. Forty audits an hour is generous for deploys and easy to exhaust from a matrix build fanning out across environments.
What our audit reports about this
Every item below is measured directly, not inferred. Run it against your own site and the result names the exact rule or header responsible.
- A per-category score and a severity on every finding, so a pipeline can assert on specifics rather than on a single number.
- A comparison endpoint reporting exactly which findings are new since the previous run, for regression gating.
- Meaningful exit-worthy status codes and JSON error bodies, so a failed audit is distinguishable from a failing site.
- Deterministic results, so a build does not fail intermittently on an unchanged site.
For agents and scripts, the same measurement is at
/api/v1/summary?url=yoursite.com —
see the API documentation.
Related questions
Should I fail the build on any score drop?
No. Scores move slightly with ordinary content changes, and a build that cries wolf gets bypassed within a fortnight. Fail on new critical findings and on the specific states that must never be true.
Where in the pipeline should this run?
After deploy, against the production URL. Running it against a preview environment measures the preview environment, which usually has different headers, a different certificate and often a blanket robots block.
What if the site is behind authentication?
Then the audit sees the login page, which is what any crawler or visitor sees, and the results describe that page. Public-facing measurement is only meaningful against publicly reachable URLs.
Will this slow my pipeline down?
A summary call is one to eight seconds. Run it as a separate job after deploy rather than in the critical path and it costs nothing you will notice.
Read next
How do I monitor a website for regressions automatically?
Catching the silent breakages — an expired certificate, a stripped header, a robots.txt that started blocking Google — before anyone reports them.
ReadHow do I audit a website from the command line?
Getting a complete, machine-readable audit with one curl command — and pulling out just the parts you need with jq.
ReadWhat is a deterministic website audit?
Why running the same audit twice should give the same answer, what breaks that property, and why it matters more than accuracy.
ReadCan an AI agent fix my website for me?
What agents can genuinely do unattended, what still needs a person, and how to set up the loop so mistakes are caught.
Read