fail_on and max_issue_rate; SEODiff returns pass plus a reason.This workflow calls POST /api/v1/validate and fails the job when SEODiff returns pass: false.
Prerequisites: curl and jq are available by default on ubuntu-latest.
name: SEODiff
on:
pull_request:
permissions:
contents: read
pull-requests: write
jobs:
seodiff:
runs-on: ubuntu-latest
steps:
- name: Determine preview URL
id: preview
run: |
# Replace this with your deployment step output
echo "url=https://preview.example.com" >> "$GITHUB_OUTPUT"
- name: Validate preview with SEODiff
id: seodiff
env:
SEODIFF_API_KEY: ${{ secrets.SEODIFF_API_KEY }}
SEODIFF_BASE_URL: ${{ steps.preview.outputs.url }}
run: |
set -euo pipefail
HTTP_CODE=$(curl -sS -o seodiff.json -w "%{http_code}" -X POST "https://api.seodiff.io/api/v1/validate" \
-H "Authorization: Bearer $SEODIFF_API_KEY" \
-H "Content-Type: application/json" \
-d "{\
\"base_url\": \"$SEODIFF_BASE_URL\",\
\"preset\": \"fast\",\
\"fail_on\": \"fetch_errors,non200_status,schema_missing_required,placeholder_hits\",\
\"max_issue_rate\": 10,\
\"wait\": true,\
\"timeout_seconds\": 180\
}")
echo "http_code=$HTTP_CODE" >> "$GITHUB_OUTPUT"
echo "report_url=$(jq -r '.report_url // ""' seodiff.json)" >> "$GITHUB_OUTPUT"
echo "pass=$(jq -r '.pass // false' seodiff.json)" >> "$GITHUB_OUTPUT"
# 200 = pass, 409 = fail, 202 = timed out waiting
if [ "$HTTP_CODE" != "200" ]; then
echo "SEODiff failed (http=$HTTP_CODE): $(jq -r '.reason // ""' seodiff.json)"
cat seodiff.json
exit 1
fi
Use /validate when you want a single request that returns a clear pass/fail decision. Use /scan when you want to enqueue work and poll later.
Fetch a PR-comment-ready Markdown summary and post it as a comment.
- name: Comment on PR
if: always()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEODIFF_API_KEY: ${{ secrets.SEODIFF_API_KEY }}
run: |
SUMMARY_PATH=$(jq -r '.summary_markdown_url // ""' seodiff.json)
if [ -n "$SUMMARY_PATH" ] && [ "$SUMMARY_PATH" != "null" ]; then
curl -sS -H "Authorization: Bearer $SEODIFF_API_KEY" "https://api.seodiff.io$SUMMARY_PATH" > seodiff_summary.md
gh pr comment ${{ github.event.pull_request.number }} --body-file seodiff_summary.md
else
PASS=$(jq -r '.pass // false' seodiff.json)
REASON=$(jq -r '.reason // ""' seodiff.json)
REPORT=$(jq -r '.report_url // ""' seodiff.json)
BODY="## SEODiff\n\n**Pass:** ${PASS}\n\n${REASON}\n\n${REPORT}"
gh pr comment ${{ github.event.pull_request.number }} --body "$BODY"
fi
fetch_errors and non200_statusschema_missing_requiredplaceholder_hitsmax_issue_rate gradually.wait=true, SEODiff returns 200 for pass and 409 for fail.202 with a running status. Treat this as non-pass for strict CI, or poll asynchronously if your workflow allows it.GET /api/v1/scans/{id}/summary.md for a PR-comment-ready summary. (Use summary_markdown_url from /validate.)SEODIFF_API_KEY exists in repo secrets.timeout_seconds for slower preview deployments.