How-to June 1, 2026 · 5 min read

Publishing Your OpenAPI Spec from CI/CD

Why manual spec publishing creates drift, and how to wire SpecVault into your pipeline so every deploy automatically updates the registry.

A spec registry is only useful if it reflects what's actually deployed. The moment publishing becomes a manual step, it starts falling behind. Someone forgets to update it after a hotfix. A new engineer doesn't know the process. A Friday deployment skips it entirely. Six months later, the registry has four services, the codebase has eight, and the diff results are wrong because they're based on a spec that's three releases old.

The fix is simple: make publishing automatic. One curl command at the end of your deploy step, triggered by your CI/CD pipeline, means the registry is always in sync with what's running.

The two publish methods

SpecVault supports two ways to publish a spec:

  • URL fetch — you tell SpecVault the URL of your live /openapi.json endpoint, and it fetches the spec at publish time. Best when your API serves its own spec.
  • File upload — you upload the spec file directly. Best when the spec is generated during the build process (e.g. from code annotations) but not served at a URL.

The single curl command

At its core, publishing is just one HTTP call:

curl -sf -X POST \
  "https://specvault.sentinelflux.in/api/services/YOUR_SERVICE_ID/specs" \
  -H "Authorization: Bearer $SPECVAULT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"spec_url": "https://api.yourcompany.com/openapi.json"}'

That's it. SpecVault fetches the spec, stores it, diffs it against the previous version, and if there are breaking changes, sends alerts to all subscribers — all synchronously in the same request.

GitHub Actions integration

Add this step at the end of your deploy job:

- name: Publish spec to SpecVault
  run: |
    curl -sf -X POST \
      "https://specvault.sentinelflux.in/api/services/${{ secrets.SPECVAULT_SERVICE_ID }}/specs" \
      -H "Authorization: Bearer ${{ secrets.SPECVAULT_TOKEN }}" \
      -H "Content-Type: application/json" \
      -d '{"spec_url": "${{ vars.API_BASE_URL }}/openapi.json"}'

Store SPECVAULT_TOKEN and SPECVAULT_SERVICE_ID in your repository's Actions Secrets. Use a repository variable for API_BASE_URL since it's not sensitive.

Publish after deploy, not after tests

An important subtlety: run the publish step after the deploy succeeds, not after tests pass. The diff should reflect what's actually running in production. If you publish from a branch that didn't deploy, SpecVault's version history diverges from reality — exactly the drift you're trying to avoid.

GitLab CI integration

publish-spec:
  stage: publish
  image: alpine/curl
  script:
    - curl -sf -X POST
        "${SPECVAULT_URL}/api/services/${SPECVAULT_SERVICE_ID}/specs"
        -H "Authorization: Bearer ${SPECVAULT_TOKEN}"
        -H "Content-Type: application/json"
        -d "{\"spec_url\": \"${API_BASE_URL}/openapi.json\"}"
  only:
    - main
  needs: [deploy]

What to do with the response

The publish endpoint returns a JSON body including the version number and breaking_changes count. You can use this to fail the pipeline if breaking changes were detected — useful if you want to enforce that breaking changes always go through a deliberate process:

RESPONSE=$(curl -sf -X POST ... )
BREAKING=$(echo $RESPONSE | jq '.breaking_changes')
if [ "$BREAKING" -gt "0" ]; then
  echo "⚠️  $BREAKING breaking change(s) detected. Subscribers have been alerted."
  # exit 1  # uncomment to fail the pipeline on breaking changes
fi

One token per environment

Create separate API tokens for staging and production pipelines. That way, if one token is ever compromised or needs rotation, you can revoke it without affecting the other environment. Token names appear in the audit log, so you can see at a glance which pipeline published each version.