Add API Drift Detection to Your CI/CD Pipeline

API drift is sneaky. A third-party provider silently changes a response field, and everything looks fine in your tests — because your test mocks are frozen in time. The first sign of a problem is a production error three weeks later.

The fix is to check for API drift at deployment time, not after the fact. When you integrate Rumbliq into your CI/CD pipeline, you'll know about breaking API changes before your code ships. If an API your app depends on has changed its schema since the last deploy, your pipeline flags it and you can decide how to handle it.

This guide covers:


Prerequisites


Step 1: Create a CI-Scoped API Key

Don't use your personal API key in CI. Create a dedicated one so you can revoke it independently.

  1. Log into rumbliq.com
  2. Navigate to your avatar → API Keys
  3. Click New API Key
  4. Label it something like ci-deployment-check
  5. Copy the key — you'll only see it once

Store this as a secret in your CI environment:


Step 2: Find Your Monitor IDs

Your pipeline will check specific monitors — the ones covering APIs critical to the code you're deploying. Get the IDs from the Rumbliq API:

curl https://rumbliq.com/v1/monitors \
  -H "Authorization: Bearer dk_live_your_api_key" \
  | jq '.data.monitors[] | {id, name}'

The list endpoint returns the standard Rumbliq envelope — the monitors live under data.monitors. Example output:

{
  "id": "mon_abc123",
  "name": "Stripe Payment Intents"
}
{
  "id": "mon_def456",
  "name": "Twilio SMS API"
}
{
  "id": "mon_ghi789",
  "name": "GitHub OAuth"
}

Note the IDs for the monitors you want to check in CI. You can also get them from the Rumbliq dashboard URL when viewing a monitor: rumbliq.com/monitors/mon_abc123.


Step 3: Trigger a Check and Inspect the Result

The core CI pattern is:

  1. Trigger a fresh check on a monitor via the API
  2. Inspect the result — if breaking drift was detected, fail the job (or warn, depending on your policy)

POST /v1/monitors/:id/check runs the check synchronously and returns the completed check record in the response body — there's nothing to poll. The response uses the standard Rumbliq envelope, so the check lives under data:

{
  "data": {
    "id": "chk_abc123",
    "status_code": 200,
    "has_breaking_changes": false,
    "drift": [],
    "executed_at": "2026-03-27T12:00:00.000Z"
  },
  "error": null
}

The two fields the pipeline cares about are has_breaking_changes (boolean) and drift (an array of change objects; empty when the schema is stable). Each drift entry looks like { "type", "path", "previousType", "currentType", "severity" }, where severity is one of breaking, warning, or info.

Here's a bash script that implements this pattern:

#!/bin/bash
# rumbliq-check.sh — Run a Rumbliq drift check and fail if breaking changes are detected

set -e

MONITOR_ID="${1:?Monitor ID required as first argument}"
API_KEY="${RUMBLIQ_API_KEY:?RUMBLIQ_API_KEY env var required}"
BASE_URL="https://rumbliq.com/v1"

echo "🔍 Triggering drift check for monitor: $MONITOR_ID"

# Trigger a check — this runs synchronously and returns the completed check
RESPONSE=$(curl -s -X POST "$BASE_URL/monitors/$MONITOR_ID/check" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json")

# Surface API-level errors (rate limits, plan limits, unreachable endpoint, etc.)
ERROR_CODE=$(echo "$RESPONSE" | jq -r '.error.code // empty')
if [ -n "$ERROR_CODE" ]; then
  echo "❌ Check request failed: $ERROR_CODE"
  echo "$RESPONSE" | jq '.error'
  exit 1
fi

BREAKING=$(echo "$RESPONSE" | jq -r '.data.has_breaking_changes')
DRIFT_COUNT=$(echo "$RESPONSE" | jq '.data.drift | length')

if [ "$BREAKING" = "true" ]; then
  echo "🚨 Breaking API changes detected on monitor $MONITOR_ID"
  echo "$RESPONSE" | jq '.data.drift'
  exit 1
elif [ "$DRIFT_COUNT" -gt 0 ]; then
  echo "⚠️  Non-breaking drift detected (additive/informational changes). Review recommended."
  echo "$RESPONSE" | jq '.data.drift'
  # Non-breaking: exit 0 to allow pipeline to continue
  exit 0
else
  echo "✅ No drift detected. API schema is stable."
  exit 0
fi

Step 4: GitHub Actions Integration

Here's a full workflow that runs drift checks before deploying to production:

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  api-drift-check:
    name: Check for API Drift
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Check Stripe API drift
        env:
          RUMBLIQ_API_KEY: ${{ secrets.RUMBLIQ_API_KEY }}
        run: |
          chmod +x ./scripts/rumbliq-check.sh
          ./scripts/rumbliq-check.sh mon_abc123

      - name: Check Twilio API drift
        env:
          RUMBLIQ_API_KEY: ${{ secrets.RUMBLIQ_API_KEY }}
        run: |
          ./scripts/rumbliq-check.sh mon_def456

      - name: Check GitHub OAuth drift
        env:
          RUMBLIQ_API_KEY: ${{ secrets.RUMBLIQ_API_KEY }}
        run: |
          ./scripts/rumbliq-check.sh mon_ghi789

  test:
    name: Run Tests
    runs-on: ubuntu-latest
    needs: api-drift-check  # Don't run tests if API drift check fails
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      - run: bun run test

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: [api-drift-check, test]  # Both must pass before deploying
    steps:
      - uses: actions/checkout@v4
      # Your deploy steps here
      - name: Deploy to production
        run: echo "Deploying..."

The needs directive ensures:

  1. Drift checks run first
  2. Tests run only if drift checks pass
  3. Deployment runs only if both pass

If an API your code depends on has changed its schema, the pipeline stops before tests and definitely before deployment.


Step 5: Check Multiple Monitors in Parallel

For repos that depend on many APIs, checking them sequentially adds latency to your pipeline. Use matrix jobs to run checks in parallel:

jobs:
  api-drift-check:
    name: Check API Drift (${{ matrix.monitor.name }})
    runs-on: ubuntu-latest
    strategy:
      matrix:
        monitor:
          - name: Stripe
            id: mon_abc123
          - name: Twilio
            id: mon_def456
          - name: GitHub OAuth
            id: mon_ghi789
          - name: Shopify
            id: mon_jkl012
    steps:
      - uses: actions/checkout@v4
      - name: Check ${{ matrix.monitor.name }} drift
        env:
          RUMBLIQ_API_KEY: ${{ secrets.RUMBLIQ_API_KEY }}
        run: |
          chmod +x ./scripts/rumbliq-check.sh
          ./scripts/rumbliq-check.sh ${{ matrix.monitor.id }}

All four checks run simultaneously. If any of them detect breaking changes, the matrix job fails and the downstream deploy job is blocked.


GitLab CI Integration

The same pattern works in GitLab CI:

# .gitlab-ci.yml
stages:
  - drift-check
  - test
  - deploy

variables:
  RUMBLIQ_BASE_URL: "https://rumbliq.com/v1"

.drift_check_template: &drift_check
  stage: drift-check
  image: alpine:latest
  before_script:
    - apk add --no-cache curl jq bash
  script:
    - chmod +x ./scripts/rumbliq-check.sh
    - ./scripts/rumbliq-check.sh $MONITOR_ID

check:stripe:
  <<: *drift_check
  variables:
    MONITOR_ID: "mon_abc123"

check:twilio:
  <<: *drift_check
  variables:
    MONITOR_ID: "mon_def456"

check:github-oauth:
  <<: *drift_check
  variables:
    MONITOR_ID: "mon_ghi789"

run_tests:
  stage: test
  needs: ["check:stripe", "check:twilio", "check:github-oauth"]
  script:
    - bun install
    - bun run test

deploy_production:
  stage: deploy
  needs: ["run_tests"]
  script:
    - echo "Deploying..."
  only:
    - main

Store RUMBLIQ_API_KEY as a masked CI/CD variable in your GitLab project settings.


Handling Failures Gracefully

Not every drift check should block deployment. Consider a tiered approach:

Blocking checks (exit 1 on breaking changes)

Use for APIs where a schema change would definitely break your code:

Warning-only checks (exit 0, log the warning)

Use for APIs where changes are low risk or you have defensive parsing:

You can modify the rumbliq-check.sh script to accept a --warn-only flag:

# Warn-only mode: log drift but don't fail the pipeline
if [ "$BREAKING" = "true" ] && [ "$WARN_ONLY" != "true" ]; then
  echo "🚨 Breaking changes detected — BLOCKING deploy"
  exit 1
elif [ "$BREAKING" = "true" ]; then
  echo "⚠️  Breaking changes detected — WARNING ONLY (warn-only mode)"
  exit 0
fi

Viewing Results in Your Pipeline

When a drift check fails, your CI output shows exactly what changed. Here's an example:

🚨 Breaking API changes detected on monitor mon_abc123
[
  {
    "type": "type_changed",
    "path": "amount",
    "previousType": "number",
    "currentType": "string",
    "severity": "breaking"
  },
  {
    "type": "field_added",
    "path": "currency",
    "currentType": "string",
    "severity": "info"
  }
]

Your team immediately knows: the amount field on the Stripe response changed from a number to a string (type_changed, severity: breaking). Any code that treats amount as a numeric value needs to be updated before the next deployment.


Key Takeaways

With this setup, your pipeline becomes your first line of defense against third-party API changes. Breaking changes get caught at deploy time, not discovered by customers.

Related Posts

Start monitoring your APIs →