SEO checks in your CI/CD pipeline
Treat SEO like any other test. Call the audit API from your pipeline, fail the build when the score drops, and surface the issues in the run log — so a noindex tag, a dropped <title> or a missing meta description never ships unnoticed.
How it works
One request does it. GET /api/v1/audit returns a numeric score (0–100) and an issues array for any URL. In CI you read the score with jq and exit non-zero when it's below a threshold you choose — that fails the step, which blocks the merge or deploy.
- {
- "url": "https://example.com",
- "score": 68,
- "grade": "C",
- "issueCount": 3,
- "issues": ["Title tag is short", "Missing meta description", "No favicon"],
- "usage": { "used": 12, "dailyLimit": 100, "tier": "free" }
- }
The core check — runs in any CI
This portable script is the whole idea: audit a URL, print the issues, fail below the threshold. Every recipe below is a wrapper around it. Save it as seo-check.sh, or paste the body straight into a pipeline step.
- #!/usr/bin/env bash
- set -euo pipefail
- URL="${1:?Usage: seo-check.sh <url>}"
- MIN_SCORE="${MIN_SCORE:-80}"
- resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
- "https://www.ranknibbler.com/api/v1/audit?url=$URL")
- score=$(printf '%s' "$resp" | jq -r '.score')
- echo "SEO score for $URL: $score (threshold: $MIN_SCORE)"
- printf '%s' "$resp" | jq -r '.issues[]?' | sed 's/^/ - /'
- if [ "$score" -lt "$MIN_SCORE" ]; then
- echo "FAIL: score $score is below $MIN_SCORE"; exit 1
- fi
- echo "PASS: score $score meets the $MIN_SCORE threshold"
Set RANKNIBBLER_API_KEY as a secret in your CI (next section), and override the bar per run with MIN_SCORE=90 ./seo-check.sh https://example.com.
GitHub Actions
Drop this in .github/workflows/seo.yml. The ubuntu-latest runner already has curl and jq. It runs on every pull request and annotates the run with each issue.
- name: SEO check
- on:
- pull_request:
- workflow_dispatch:
- jobs:
- seo:
- runs-on: ubuntu-latest
- env:
- RANKNIBBLER_API_KEY: ${{ secrets.RANKNIBBLER_API_KEY }}
- URL: https://staging.example.com
- MIN_SCORE: "80"
- steps:
- - name: Audit SEO score
- run: |
- resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
- "https://www.ranknibbler.com/api/v1/audit?url=$URL")
- score=$(echo "$resp" | jq -r '.score')
- echo "SEO score: $score (min $MIN_SCORE)"
- echo "$resp" | jq -r '.issues[]?' | sed 's/^/::warning::/'
- [ "$score" -ge "$MIN_SCORE" ] || { echo "::error::SEO score $score below $MIN_SCORE"; exit 1; }
Add the key under Settings → Secrets and variables → Actions as RANKNIBBLER_API_KEY.
GitLab CI
Add a job to .gitlab-ci.yml. Store the key as a masked CI/CD variable (Settings → CI/CD → Variables).
- seo-check:
- image: alpine:latest
- variables:
- URL: "https://staging.example.com"
- MIN_SCORE: "80"
- before_script:
- - apk add --no-cache curl jq
- script:
- - resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" "https://www.ranknibbler.com/api/v1/audit?url=$URL")
- - score=$(echo "$resp" | jq -r '.score')
- - 'echo "SEO score: $score (min $MIN_SCORE)"'
- - echo "$resp" | jq -r '.issues[]?'
- - test "$score" -ge "$MIN_SCORE"
Bitbucket Pipelines
Add this to bitbucket-pipelines.yml. Store the key as a repository variable (Repository settings → Repository variables, ticked as secured).
- pipelines:
- pull-requests:
- '**':
- - step:
- name: SEO check
- image: alpine:latest
- script:
- - apk add --no-cache curl jq
- - resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" "https://www.ranknibbler.com/api/v1/audit?url=$URL")
- - score=$(echo "$resp" | jq -r '.score')
- - 'echo "SEO score: $score (min $MIN_SCORE)"'
- - echo "$resp" | jq -r '.issues[]?'
- - test "$score" -ge "$MIN_SCORE"
CircleCI
Add to .circleci/config.yml. The cimg/base image ships with curl and jq. Set the key as a project environment variable.
- version: 2.1
- jobs:
- seo-check:
- docker:
- - image: cimg/base:current
- environment:
- URL: https://staging.example.com
- MIN_SCORE: "80"
- steps:
- - run:
- name: SEO check
- command: |
- resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" "https://www.ranknibbler.com/api/v1/audit?url=$URL")
- score=$(echo "$resp" | jq -r '.score')
- echo "SEO score: $score (min $MIN_SCORE)"
- echo "$resp" | jq -r '.issues[]?'
- test "$score" -ge "$MIN_SCORE"
- workflows:
- seo:
- jobs:
- - seo-check
Docker / any runner
No pipeline file needed — run the check in a throwaway container anywhere (Jenkins, Drone, a cron box, your laptop).
- docker run --rm -e RANKNIBBLER_API_KEY alpine sh -c \
- 'apk add -q curl jq && curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
- "https://www.ranknibbler.com/api/v1/audit?url=https://example.com" | jq "{score, grade, issues}"'
Gate on specific issues, not just the score
Sometimes one problem should always block a deploy regardless of the overall score — a page going noindex, say. The issues array makes that a one-liner.
- resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
- "https://www.ranknibbler.com/api/v1/audit?url=$URL")
- if echo "$resp" | jq -e '.issues | index("Missing meta description")' >/dev/null; then
- echo "Blocking: meta description is missing"; exit 1
- fi
And to guard more than one page in a run, loop over a list and fail if any falls short:
- fail=0
- for U in https://example.com https://example.com/pricing https://example.com/blog; do
- s=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
- "https://www.ranknibbler.com/api/v1/audit?url=$U" | jq -r '.score')
- echo "$U -> $s"
- [ "$s" -ge "${MIN_SCORE:-80}" ] || fail=1
- done
- exit $fail
Rate limits & tips
- Each audited URL is one request. The free tier allows 100 requests per key per day, resetting at 00:00 UTC — every response carries a
usageobject so you always know where you stand. - Run the check on pull requests and deploys, not every push, and audit your handful of money pages rather than the whole site, to stay well inside the limit.
- Pass the key as the
X-API-Keyheader via a secret — never in the URL — so it stays out of logs. - Point the check at your staging or preview URL so a regression is caught before it reaches production.
- Other endpoints work the same way: gate on security headers, accessibility or indexability with the same pattern.
How to add it, end to end
1. Create an API key
Create a free account, then generate a key in your API dashboard. It's shown once — copy it.
2. Store the key as a CI secret
Add it to your CI as a secret named RANKNIBBLER_API_KEY (GitHub Actions secret, GitLab masked variable, Bitbucket repository variable, CircleCI project env var).
3. Add the SEO check step
Paste the recipe for your platform, set URL and MIN_SCORE, and commit. The build now fails whenever the SEO score drops below your bar.
Frequently asked questions
Can I fail a build when my SEO score drops?
Yes. The audit endpoint returns a numeric score from 0 to 100. Read it with jq and exit non-zero when it falls below a threshold you set, and the pipeline step fails — blocking the merge or deploy. The recipes on this page do exactly that with a one-line test, and they print the list of issues so you can see what regressed.
Where do I store my RankNibbler API key in CI?
As a secret, never in the YAML. Use a GitHub Actions repository secret, a GitLab CI/CD masked variable, a Bitbucket repository variable or a CircleCI project environment variable, named RANKNIBBLER_API_KEY, and pass it as the X-API-Key header. Keeping it in a header rather than the URL keeps it out of logs and browser history.
Which CI platforms does this work with?
Any of them. The check is a plain HTTPS request plus jq, so it runs anywhere you have a shell — GitHub Actions, GitLab CI, Bitbucket Pipelines, CircleCI, Jenkins, Azure Pipelines, Drone or a bare Docker container. This page has ready-to-paste files for the common ones and a portable script for the rest.
How many URLs can I check in one pipeline run?
Each audited URL is one API request. The free tier allows 100 requests per key per day, so a typical run that checks a handful of key pages is well within limits. Loop over a list of URLs and fail the build if any one is below threshold; every response includes a usage object showing how many requests you've used today.
Do I need to install anything in my CI runner?
Only curl and jq, and most runners already have them — GitHub's ubuntu-latest and CircleCI's cimg/base ship with both. On a minimal Alpine image, add them with a single apk add --no-cache curl jq before the check. No SDK, browser or build dependency is required.
Start gating your deploys
Create a free key, paste the recipe for your CI, and ship with an SEO safety net — it's free.