CI

Run Dino in CI: the GitHub Action, pinning, credentials, failing a build, partial coverage, reading the result, breaking changes, GitHub code scanning, and the same pattern for any CI system.

Run Dino after the API you're verifying is deployed: it tests the running API, not your source code. It returns an exit code your pipeline branches on and a DinoResult document you can keep.

GitHub Actions

name: API verification
on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  dino:
    runs-on: ubuntu-latest
    steps:
      - uses: Dino-HQ/dino/.github/actions/scan@v1
        with:
          api-url: ${{ secrets.API_URL }}
          api-token: ${{ secrets.API_TOKEN }}   # optional
          cli-version: 1.4.0
          fail-on-high: true
  • Pin the CLI. @v1 takes backward-compatible fixes to the action and never a breaking change (that would be v2). cli-version pins the CLI, so CLI upgrades are deliberate. Don't use @main, which moves with every change. For a hardened workflow, pin the action to a full commit SHA instead of @v1.
  • Credentials. api-token is the action's input for a bearer token. Pass it from a repository secret; the action sends it as Authorization: Bearer … and never writes it to disk.
  • The result. The report is uploaded as the dino-scan-report artifact, and the step's exit-code output carries Dino's exit code.
InputDefaultWhat it does
api-urlrequiredThe endpoint to verify
protocolgraphqlgraphql or rest
spec-urlOpenAPI URL or path, required for rest
api-tokenBearer token for the target API
fail-on-highfalseFail (exit 3) on any HIGH or CRITICAL finding
accept-partialfalseTreat partial coverage as success instead of exit 6
fail-on-breakingfalseAlso fail (exit 3) on breaking schema changes; see Breaking changes
formatmarkdownReport format: markdown, json, or sarif for GitHub code scanning
upload-sariftrueWith sarif: upload to code scanning (set false to upload it yourself)
github-tokengithub.tokenWith sarif: reads the previous code scanning analysis and its state
cli-versionlatestCLI version to install

Failing a build

The exit code is the signal. Nothing needs to parse the report.

ExitMeaning in CI
0Clean, or findings below your gate: pass
3A gate failed (--fail-on-high, --fail-on-breaking): do not ship
6Partial coverage: some checks couldn't complete, so this is not a pass
4Transient (network, rate limit): retry
2, 5Usage or config error: fix the input
70Dino crashed: report it

Partial coverage means Dino couldn't verify everything it planned, for example because introspection was limited or a check couldn't run. The verdict is honest about it: verdict.coverage is "partial" and verdict.reasons says why. Decide explicitly whether that's acceptable. accept-partial (or --accept-partial) turns 6 into 0; it never hides a failed gate. The full table and precedence: Machine contracts.

Breaking changes

fail-on-breaking runs dino diff, which compares the API with the snapshot saved by the previous run. On a fresh CI runner there is no previous run, so the first build only saves a baseline. Keep the .dino/snapshots folder between runs with a cache:

      - uses: actions/cache@v4
        with:
          path: .dino/snapshots
          key: dino-snapshots-${{ github.ref_name }}-${{ github.run_id }}
          restore-keys: dino-snapshots-${{ github.ref_name }}-
      - uses: Dino-HQ/dino/.github/actions/scan@v1
        with:
          api-url: ${{ secrets.API_URL }}
          cli-version: 1.4.0
          fail-on-breaking: true

GitHub code scanning

With format: sarif, Dino's findings become alerts in the repository's Security tab, next to your other code scanning tools. Security findings (access control, data leaks, CORS, missing rate limits and similar) are security alerts with a severity; contract and deprecation findings are quality alerts. SARIF needs CLI 1.3.0 or later.

name: API verification (code scanning)
on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read
  security-events: write   # upload SARIF, read the previous Dino analysis
  actions: read            # read the previous run's Dino state (needed on private repositories)

concurrency:
  group: dino-sarif-${{ github.ref }}
  cancel-in-progress: false

jobs:
  dino:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4   # a committed spec or .dino.yml gives alerts a stable file
      - uses: Dino-HQ/dino/.github/actions/scan@v1
        with:
          api-url: ${{ secrets.API_URL }}
          cli-version: 1.4.0
          format: sarif
  • Untested is never fixed. GitHub closes every alert missing from an upload. Dino reconciles each upload with the previous one: an alert closes only when Dino re-tested that exact operation with the same test plan. A partial run uploads what it verified and carries every alert it didn't test forward, unchanged.
  • When Dino can't tell, it uploads nothing. If the previous analysis or its state can't be read, or another Dino upload landed during the scan, no SARIF is written, existing alerts stay as they are, and the step keeps the scan's exit code. A run that reached nothing (unreachable target, every tool failed, nothing in scope) never uploads. The sarif-written output says which happened.
  • One writer per ref. Every workflow that uploads Dino SARIF must use the same concurrency group, dino-sarif-${{ github.ref }}. Two uploads at once could each start from the same previous analysis and close an alert neither re-tested.
  • State. The action keeps the state the next run needs as a workflow artifact (dino-sarif-state-…). Keep artifact retention longer than the gap between scans. If it expires, Dino writes no SARIF until you run one complete scan with --sarif-rebaseline, which starts over deliberately.
  • Where alerts point. Each alert is attached to a file git tracks: your local OpenAPI spec or SDL, else .dino.yml, else the workflow file. A scan narrowed with --tools or --modules uploads under its own category, so it never closes a full scan's alerts.
  • Outside GitHub Actions, dino scan --format sarif writes SARIF only for a complete run, for other SARIF consumers. Exit codes, refusals and the identifiers are in Machine contracts.

Any other CI

The same three steps work anywhere with a shell: install a pinned version, run the scan, branch on the exit code.

bash -o pipefail -c 'curl -fsSL https://usedino.dev/install.sh | sh -s 1.4.0'
export PATH="$HOME/.local/bin:$PATH"

dino scan --endpoint "$API_URL" --fail-on-high --format json > dino-result.json
code=$?   # capture it before any other command runs

echo "Dino exit code: $code"
exit "$code"
  • Credentials. Keep secrets in your CI's secret store and pass them at run time: --token "$API_TOKEN" or --header "X-Api-Key: $API_KEY" on the command line, or commit a .dino.yml whose auth names the environment variables to read (valueEnv, clientIdEnv, clientSecretEnv). Dino never writes a secret to the file. See Configuration.
  • The result. dino-result.json is the DinoResult document: keep it as a build artifact, or read the verdict with jq '.verdict'. Errors (exit 2, 4, 5, 70) put a JSON envelope on the last line of stderr instead. See Machine contracts.
  • No install. With Node.js 22+ on the runner you can skip installing: npx -y @dino-hq/cli@1.4.0 scan ….
  • Breaking changes. Run dino diff --endpoint "$API_URL" --fail-on-breaking before dino scan and keep .dino/snapshots between runs with your CI's cache. diff compares with the latest saved snapshot and scan saves a new one, so a diff after the scan would compare the API with itself. The GitHub Action already runs them in this order.