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:
- Setting up the Rumbliq API key for CI use
- A reusable drift-check pattern using the Rumbliq API
- Full GitHub Actions workflow example
- GitLab CI equivalent
- Handling failures: blocking vs. non-blocking checks
Prerequisites
- A Rumbliq account with at least one monitor configured
- An API key for CI use (we'll create one below)
- Your CI platform: GitHub Actions or GitLab CI (the concepts apply to any platform)
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.
- Log into rumbliq.com
- Navigate to your avatar → API Keys
- Click New API Key
- Label it something like
ci-deployment-check - Copy the key — you'll only see it once
Store this as a secret in your CI environment:
- GitHub Actions: Settings → Secrets and variables → Actions → New repository secret →
RUMBLIQ_API_KEY - GitLab CI: Settings → CI/CD → Variables → Add variable →
RUMBLIQ_API_KEY(mask it)
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:
- Trigger a fresh check on a monitor via the API
- Poll until the check completes
- 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:
- Drift checks run first
- Tests run only if drift checks pass
- 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:
- Payment processor APIs (field type changes → transaction failures)
- Auth provider APIs (removed fields → login failures)
- Core data APIs your app reads from
Warning-only checks (exit 0, log the warning)
Use for APIs where changes are low risk or you have defensive parsing:
- Analytics/telemetry APIs
- Optional data enrichment APIs
- APIs where you already handle missing fields gracefully
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
- Create a dedicated CI API key — keep it separate from personal keys for easy rotation
- Run checks early in the pipeline — before tests, before build, before deploy
- Use parallel matrix jobs — check multiple APIs simultaneously to keep pipeline fast
- Differentiate blocking vs warning — not every API drift needs to stop deployment
- Log the full diff in CI — make it easy for engineers to understand what changed
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.