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.

What the audit returns
  1. {
  2. "url": "https://example.com",
  3. "score": 68,
  4. "grade": "C",
  5. "issueCount": 3,
  6. "issues": ["Title tag is short", "Missing meta description", "No favicon"],
  7. "usage": { "used": 12, "dailyLimit": 100, "tier": "free" }
  8. }

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.

seo-check.sh — works in any shell with curl + jq
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. URL="${1:?Usage: seo-check.sh <url>}"
  4. MIN_SCORE="${MIN_SCORE:-80}"
  5. resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
  6. "https://www.ranknibbler.com/api/v1/audit?url=$URL")
  7. score=$(printf '%s' "$resp" | jq -r '.score')
  8. echo "SEO score for $URL: $score (threshold: $MIN_SCORE)"
  9. printf '%s' "$resp" | jq -r '.issues[]?' | sed 's/^/ - /'
  10. if [ "$score" -lt "$MIN_SCORE" ]; then
  11. echo "FAIL: score $score is below $MIN_SCORE"; exit 1
  12. fi
  13. 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.

.github/workflows/seo.yml
  1. name: SEO check
  2. on:
  3. pull_request:
  4. workflow_dispatch:
  5. jobs:
  6. seo:
  7. runs-on: ubuntu-latest
  8. env:
  9. RANKNIBBLER_API_KEY: ${{ secrets.RANKNIBBLER_API_KEY }}
  10. URL: https://staging.example.com
  11. MIN_SCORE: "80"
  12. steps:
  13. - name: Audit SEO score
  14. run: |
  15. resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
  16. "https://www.ranknibbler.com/api/v1/audit?url=$URL")
  17. score=$(echo "$resp" | jq -r '.score')
  18. echo "SEO score: $score (min $MIN_SCORE)"
  19. echo "$resp" | jq -r '.issues[]?' | sed 's/^/::warning::/'
  20. [ "$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).

.gitlab-ci.yml
  1. seo-check:
  2. image: alpine:latest
  3. variables:
  4. URL: "https://staging.example.com"
  5. MIN_SCORE: "80"
  6. before_script:
  7. - apk add --no-cache curl jq
  8. script:
  9. - resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" "https://www.ranknibbler.com/api/v1/audit?url=$URL")
  10. - score=$(echo "$resp" | jq -r '.score')
  11. - 'echo "SEO score: $score (min $MIN_SCORE)"'
  12. - echo "$resp" | jq -r '.issues[]?'
  13. - 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).

bitbucket-pipelines.yml
  1. pipelines:
  2. pull-requests:
  3. '**':
  4. - step:
  5. name: SEO check
  6. image: alpine:latest
  7. script:
  8. - apk add --no-cache curl jq
  9. - resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" "https://www.ranknibbler.com/api/v1/audit?url=$URL")
  10. - score=$(echo "$resp" | jq -r '.score')
  11. - 'echo "SEO score: $score (min $MIN_SCORE)"'
  12. - echo "$resp" | jq -r '.issues[]?'
  13. - 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.

.circleci/config.yml
  1. version: 2.1
  2. jobs:
  3. seo-check:
  4. docker:
  5. - image: cimg/base:current
  6. environment:
  7. URL: https://staging.example.com
  8. MIN_SCORE: "80"
  9. steps:
  10. - run:
  11. name: SEO check
  12. command: |
  13. resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" "https://www.ranknibbler.com/api/v1/audit?url=$URL")
  14. score=$(echo "$resp" | jq -r '.score')
  15. echo "SEO score: $score (min $MIN_SCORE)"
  16. echo "$resp" | jq -r '.issues[]?'
  17. test "$score" -ge "$MIN_SCORE"
  18. workflows:
  19. seo:
  20. jobs:
  21. - seo-check

Docker / any runner

No pipeline file needed — run the check in a throwaway container anywhere (Jenkins, Drone, a cron box, your laptop).

One-off audit in a container
  1. docker run --rm -e RANKNIBBLER_API_KEY alpine sh -c \
  2. 'apk add -q curl jq && curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
  3. "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.

Fail if a specific issue is present
  1. resp=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
  2. "https://www.ranknibbler.com/api/v1/audit?url=$URL")
  3. if echo "$resp" | jq -e '.issues | index("Missing meta description")' >/dev/null; then
  4. echo "Blocking: meta description is missing"; exit 1
  5. fi

And to guard more than one page in a run, loop over a list and fail if any falls short:

Check several pages
  1. fail=0
  2. for U in https://example.com https://example.com/pricing https://example.com/blog; do
  3. s=$(curl -fsS -H "X-API-Key: $RANKNIBBLER_API_KEY" \
  4. "https://www.ranknibbler.com/api/v1/audit?url=$U" | jq -r '.score')
  5. echo "$U -> $s"
  6. [ "$s" -ge "${MIN_SCORE:-80}" ] || fail=1
  7. done
  8. exit $fail

Rate limits & tips

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.

Create a free account   or read the quick start