GitHub Actions for DocsCI

Copy-paste-ready YAML workflows to run DocsCI on every pull request. Pick the template that fits your project structure.

Prerequisites

  1. 1.Create a DocsCI account at snippetci.com/signup and create a project.
  2. 2.Generate a DocsCI API token from Settings → Tokens.
  3. 3.Add the token to your GitHub repository as a secret named DOCSCI_TOKEN: Settings → Secrets and variables → Actions → New repository secret.
  4. 4.Create .github/workflows/ in your repository if it doesn't already exist.

Basic workflow

The simplest workflow — runs DocsCI on every push to main and every pull request. Fails the CI check if any snippet is broken or any finding exceeds threshold.

.github/workflows/docsci.ymlYAML
# .github/workflows/docsci.yml
name: DocsCI

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]
    paths:
      - 'docs/**'
      - '*.md'
      - '**/*.mdx'

jobs:
  docs-ci:
    name: Verify documentation
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@v4

      - name: Archive docs
        run: tar czf docs.tar.gz docs/ *.md 2>/dev/null || tar czf docs.tar.gz docs/

      - name: Run DocsCI
        id: docsci
        run: |
          set -euo pipefail
          RESULT=$(curl -sf -X POST https://snippetci.com/api/runs/queue \
            -H "Authorization: Bearer ${{ secrets.DOCSCI_TOKEN }}" \
            -F "docs_archive=@docs.tar.gz" \
            -w "\n%{http_code}" 2>/dev/null)
          HTTP_CODE=$(echo "$RESULT" | tail -1)
          BODY=$(echo "$RESULT" | head -n -1)
          echo "body=$BODY" >> $GITHUB_OUTPUT
          [ "$HTTP_CODE" -eq 200 ] || exit 1

      - name: Check result
        run: |
          STATUS=$(echo '${{ steps.docsci.outputs.body }}' | jq -r '.status')
          echo "DocsCI status: $STATUS"
          [ "$STATUS" = "passed" ] || exit 1
Tip: Add paths: ['docs/**', '*.md'] to the PR trigger to skip the check when only non-docs files change. This reduces unnecessary CI minutes.

Advanced: PR comments + OpenAPI

This workflow adds OpenAPI drift detection and posts a summary comment on every pull request. Requires pull-requests: write permission and the GITHUB_TOKEN (available automatically).

.github/workflows/docsci-advanced.ymlYAML
# .github/workflows/docsci-advanced.yml
name: DocsCI (Advanced)

on:
  pull_request:
    branches: [main, master]

jobs:
  docs-ci:
    name: Verify documentation
    runs-on: ubuntu-latest
    timeout-minutes: 15
    permissions:
      pull-requests: write

    steps:
      - uses: actions/checkout@v4

      - name: Archive docs with OpenAPI spec
        run: |
          tar czf docs.tar.gz docs/ *.md **/*.mdx 2>/dev/null || \
          tar czf docs.tar.gz docs/

      - name: Run DocsCI
        id: docsci
        env:
          DOCSCI_TOKEN: ${{ secrets.DOCSCI_TOKEN }}
        run: |
          set -euo pipefail
          RESULT=$(curl -sf -X POST https://snippetci.com/api/runs/queue \
            -H "Authorization: Bearer $DOCSCI_TOKEN" \
            -F "docs_archive=@docs.tar.gz" \
            -F "openapi_url=https://api.example.com/openapi.json" \
            -F "branch=${{ github.head_ref }}" \
            -F "commit_sha=${{ github.sha }}" \
            -F "pr_number=${{ github.event.number }}" \
            -F "repo=${{ github.repository }}")
          echo "result=$RESULT" >> $GITHUB_OUTPUT
          echo "$RESULT" | jq -r '.status' | grep -q "passed" || exit 1

      - name: Post PR summary
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const result = JSON.parse('${{ steps.docsci.outputs.result }}' || '{}');
            const status = result.status || 'unknown';
            const findings = result.finding_count || 0;
            const duration = ((result.duration_ms || 0) / 1000).toFixed(1);
            const icon = status === 'passed' ? '✅' : '❌';
            const body = [
              `## ${icon} DocsCI: ${status}`,
              `- **Findings:** ${findings}`,
              `- **Duration:** ${duration}s`,
              `- [View full report](https://snippetci.com/runs/${result.run_id})`,
            ].join('\n');
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body,
            });

Replace api.example.com/openapi.json with your actual OpenAPI spec URL. Remove the openapi_url line if you don't have an OpenAPI spec.

Monorepo: per-package runs

For monorepos, detect which packages changed and run DocsCI only for those packages in parallel. Uses a matrix strategy for concurrent per-package execution.

.github/workflows/docsci-monorepo.ymlYAML
# .github/workflows/docsci-monorepo.yml
name: DocsCI (Monorepo)

on:
  pull_request:
    paths:
      - 'packages/*/docs/**'
      - 'packages/*/README.md'
      - 'docs/**'

jobs:
  detect-changed-packages:
    runs-on: ubuntu-latest
    outputs:
      packages: ${{ steps.changed.outputs.packages }}
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 2 }
      - id: changed
        run: |
          PKGS=$(git diff --name-only HEAD~1..HEAD \
            | grep -E '^packages/[^/]+/' \
            | awk -F/ '{print $2}' | sort -u | jq -Rcn '[inputs]')
          echo "packages=$PKGS" >> $GITHUB_OUTPUT

  docs-ci:
    needs: detect-changed-packages
    if: ${{ needs.detect-changed-packages.outputs.packages != '[]' }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        package: ${{ fromJson(needs.detect-changed-packages.outputs.packages) }}
    name: DocsCI — ${{ matrix.package }}

    steps:
      - uses: actions/checkout@v4
      - name: Run DocsCI for ${{ matrix.package }}
        run: |
          cd packages/${{ matrix.package }}
          tar czf /tmp/docs.tar.gz docs/ *.md 2>/dev/null || \
          tar czf /tmp/docs.tar.gz docs/
          curl -sf -X POST https://snippetci.com/api/runs/queue \
            -H "Authorization: Bearer ${{ secrets.DOCSCI_TOKEN }}" \
            -F "docs_archive=@/tmp/docs.tar.gz" \
            -F "project=${{ matrix.package }}" \
          | jq -e '.status == "passed"'

Adjust the packages/*/docs/** glob to match your monorepo structure.

Nightly scheduled run

Run a full DocsCI pass nightly to catch drift that wasn't introduced by a PR (e.g., your API changed externally, or a runtime version updated).

.github/workflows/docsci-nightly.ymlYAML
# .github/workflows/docsci-nightly.yml
name: DocsCI Nightly

on:
  schedule:
    - cron: '0 2 * * *'   # 2 AM UTC daily
  workflow_dispatch:        # allow manual trigger

jobs:
  nightly-docs-check:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - uses: actions/checkout@v4

      - name: Archive all docs
        run: find . -name '*.md' -o -name '*.mdx' | \
          tar czf docs.tar.gz --files-from=-

      - name: Run full DocsCI suite
        run: |
          RESULT=$(curl -sf -X POST https://snippetci.com/api/runs/queue \
            -H "Authorization: Bearer ${{ secrets.DOCSCI_TOKEN }}" \
            -F "docs_archive=@docs.tar.gz" \
            -F "full_suite=true")
          echo "$RESULT" | jq .
          echo "$RESULT" | jq -e '.status == "passed"' || \
            (echo "::error::DocsCI found $(echo $RESULT | jq .finding_count) issues" && exit 1)

Secrets & environment variables

Secret / VariableRequiredDescription
DOCSCI_TOKENRequiredDocsCI API token. Generate from Settings → Tokens.
GITHUB_TOKENAuto-injectedGitHub's built-in token — used for posting PR comments. No setup needed.
DOCSCI_PROJECT_IDOptionalPin runs to a specific project. Otherwise uses the default project for the token.

Tips & troubleshooting

The workflow fails with 401 Unauthorized

Check that the DOCSCI_TOKEN secret is set in Settings → Secrets. The token must match the project you're sending docs to. Tokens expire after 1 year by default.

The tar command fails — no docs directory found

Update the tar command to match your docs directory structure. Use `find . -name '*.md'` to list files before archiving. The docs archive must contain at least one .md or .mdx file.

Runs time out on large doc sets

Increase timeout-minutes to 20-30. For very large repos, use the paths filter to only trigger on documentation changes. Consider the monorepo workflow to parallelize across packages.

I want to block merges on findings

DocsCI exits with code 1 when status is 'failed'. The `jq -e '.status == "passed"'` check already blocks the CI step. GitHub will block the PR merge if you configure DocsCI as a required status check in branch protection rules.