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 '.[].id, .[].name'

Example output:

"mon_abc123"
"Stripe Payment Intents"
"mon_def456"
"Twilio SMS API"
"mon_ghi789"
"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 Poll for Results

The core CI pattern is:

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

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"
MAX_WAIT=120  # seconds
POLL_INTERVAL=5

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

# Trigger a check
TRIGGER_RESPONSE=$(curl -s -X POST "$BASE_URL/monitors/$MONITOR_ID/check" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json")

CHECK_ID=$(echo "$TRIGGER_RESPONSE" | jq -r '.id')
if [ "$CHECK_ID" = "null" ] || [ -z "$CHECK_ID" ]; then
  echo "❌ Failed to trigger check. Response: $TRIGGER_RESPONSE"
  exit 1
fi

echo "⏳ Check triggered: $CHECK_ID. Waiting for result..."

# Poll for completion
ELAPSED=0
while [ $ELAPSED -lt $MAX_WAIT ]; do
  sleep $POLL_INTERVAL
  ELAPSED=$((ELAPSED + POLL_INTERVAL))

  CHECK_RESULT=$(curl -s "$BASE_URL/checks/$CHECK_ID" \
    -H "Authorization: Bearer $API_KEY")

  STATUS=$(echo "$CHECK_RESULT" | jq -r '.status')

  case "$STATUS" in
    "completed")
      DRIFT=$(echo "$CHECK_RESULT" | jq -r '.driftDetected')
      BREAKING=$(echo "$CHECK_RESULT" | jq -r '.hasBreakingChanges')

      if [ "$BREAKING" = "true" ]; then
        echo "🚨 Breaking API changes detected on monitor $MONITOR_ID"
        echo "$CHECK_RESULT" | jq '.changes'
        exit 1
      elif [ "$DRIFT" = "true" ]; then
        echo "⚠️  Non-breaking drift detected (additive changes). Review recommended."
        echo "$CHECK_RESULT" | jq '.changes'
        # Non-breaking: exit 0 to allow pipeline to continue
        exit 0
      else
        echo "✅ No drift detected. API schema is stable."
        exit 0
      fi
      ;;
    "error")
      echo "❌ Check failed with error:"
      echo "$CHECK_RESULT" | jq '.error'
      exit 1
      ;;
    "pending"|"running")
      echo "  Still running... ($ELAPSED/${MAX_WAIT}s)"
      ;;
    *)
      echo "  Unknown status: $STATUS"
      ;;
  esac
done

echo "⏰ Timed out waiting for check to complete after ${MAX_WAIT}s"
exit 1

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 (Stripe Payment Intents)

Changes detected:
{
  "field": "amount",
  "changeType": "type_change",
  "before": "number",
  "after": "string",
  "breaking": true
}
{
  "field": "currency",
  "changeType": "required_to_optional",
  "breaking": false
}

Your team immediately knows: the amount field on the Stripe response changed from a number to a string. 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 →