Claude-code-plugins-plus-skills navan-upgrade-migration
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/navan-pack/skills/navan-upgrade-migration" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-navan-upgrade-migration && rm -rf "$T"
plugins/saas-packs/navan-pack/skills/navan-upgrade-migration/SKILL.mdNavan Upgrade Migration
Overview
Defensive patterns for maintaining Navan API integrations over time. Navan does not publicly version their API, publish a changelog, or guarantee backward compatibility. Every API response should be treated as potentially different from the last.
Prerequisites
- Existing Navan API integration in production
- OAuth credentials (
,client_id
) stored in a secret managerclient_secret - Baseline API response snapshots for comparison (see Step 1)
,curl
, andjq
for schema comparisondiff
Instructions
Step 1 — Capture Response Baselines
Store known-good API responses as reference schemas. Compare against these regularly to detect drift.
TOKEN=$(curl -s -X POST "https://api.navan.com/ta-auth/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=$NAVAN_CLIENT_ID&client_secret=$NAVAN_CLIENT_SECRET" \ | jq -r '.access_token') BASELINE_DIR="navan-api-baselines/$(date +%Y%m%d)" mkdir -p "$BASELINE_DIR" # Capture response structure (keys only, no values) for ENDPOINT in users bookings; do curl -s -H "Authorization: Bearer $TOKEN" \ "https://api.navan.com/v1/${ENDPOINT}?page=0&size=1" \ | jq '[.data[] | keys] | .[0]' > "$BASELINE_DIR/${ENDPOINT}-schema.json" 2>/dev/null echo "Captured: $ENDPOINT → $(cat "$BASELINE_DIR/${ENDPOINT}-schema.json" | jq length) fields" done
Step 2 — Schema Drift Detection
Run this periodically (daily cron or CI pipeline) to detect API changes:
LATEST_BASELINE=$(ls -d navan-api-baselines/*/ | sort | tail -1) for ENDPOINT in users bookings; do CURRENT=$(curl -s -H "Authorization: Bearer $TOKEN" \ "https://api.navan.com/v1/${ENDPOINT}?page=0&size=1" \ | jq '[.data[] | keys] | .[0]' 2>/dev/null) BASELINE=$(cat "${LATEST_BASELINE}${ENDPOINT}-schema.json" 2>/dev/null) # Compare field sets ADDED=$(comm -13 <(echo "$BASELINE" | jq -r '.[]' | sort) <(echo "$CURRENT" | jq -r '.[]' | sort)) REMOVED=$(comm -23 <(echo "$BASELINE" | jq -r '.[]' | sort) <(echo "$CURRENT" | jq -r '.[]' | sort)) [ -n "$ADDED" ] && echo "WARNING: $ENDPOINT has NEW fields: $ADDED" [ -n "$REMOVED" ] && echo "CRITICAL: $ENDPOINT has REMOVED fields: $REMOVED" [ -z "$ADDED" ] && [ -z "$REMOVED" ] && echo "OK: $ENDPOINT schema unchanged" done
Step 3 — Defensive Response Parsing
Never assume a fixed schema. Use defensive patterns that tolerate changes:
# BAD: Assumes exact structure — breaks if fields are renamed or removed # jq '.trips[0].flight_number' # GOOD: Defensive parsing with fallbacks jq ' if type == "array" then .[0] // {} | { id: (.id // .uuid // .booking_id // "unknown"), flight: (.flight_number // .flight_no // .flightNumber // "N/A"), status: (.status // .booking_status // "unknown"), _extra_fields: (keys - ["id","uuid","booking_id","flight_number","flight_no", "flightNumber","status","booking_status"]) } else {error: "unexpected response type", type: type} end ' /tmp/navan-trips.json
Key defensive principles:
- Always provide fallback field names (Navan may rename without notice)
- Log unknown fields rather than ignoring them — they signal upcoming changes
- Never hard-code array lengths or object depth assumptions
- Parse dates permissively (ISO 8601, Unix timestamp, and custom formats)
Step 4 — Deprecation Signal Monitoring
Check HTTP response headers for deprecation or sunset signals:
# Capture and inspect response headers for deprecation notices curl -s -D - -o /dev/null \ -H "Authorization: Bearer $TOKEN" \ "https://api.navan.com/v1/users" \ | grep -iE "deprecat|sunset|warning|x-api-version|x-deprecated" # Check response body for deprecation warnings curl -s -H "Authorization: Bearer $TOKEN" \ "https://api.navan.com/v1/users" \ | jq '{ has_deprecation_warning: (._deprecated // .deprecated // .warning // null), has_version_header: (.api_version // ._api_version // null) }'
Step 5 — Gradual Rollout Strategy
When you detect or anticipate an API change, use feature flags to roll out handling changes gradually:
# Feature flag pattern for API response handling # Store flag in environment or config service export NAVAN_USE_NEW_TRIP_SCHEMA="${NAVAN_USE_NEW_TRIP_SCHEMA:-false}" # In your integration code, branch on the flag if [ "$NAVAN_USE_NEW_TRIP_SCHEMA" = "true" ]; then # New parsing logic for updated schema jq '.[] | {id: .booking_uuid, flight: .flight_number}' /tmp/trips.json else # Legacy parsing logic (current production) jq '.[] | {id: .id, flight: .flight_no}' /tmp/trips.json fi
Rollout procedure:
- Deploy new parsing logic behind a feature flag (flag = off)
- Enable for 5% of traffic — compare outputs between old and new parsers
- If outputs match or new parser handles additional fields, increase to 25%
- Monitor error rates at each stage for 24 hours
- Full rollout at 100% when confidence is high
- Remove old parsing logic and feature flag after 2 weeks at 100%
Step 6 — Automated Regression Testing
Run regression tests against live API responses on a schedule:
# Regression test: verify critical fields still exist FAILURES=0 USERS_RESPONSE=$(curl -s -H "Authorization: Bearer $TOKEN" \ "https://api.navan.com/v1/users") # Check required fields exist for FIELD in id email; do HAS_FIELD=$(echo "$USERS_RESPONSE" | jq ".data[0] | has(\"$FIELD\")") if [ "$HAS_FIELD" != "true" ]; then echo "REGRESSION: /v1/users missing required field: $FIELD" FAILURES=$((FAILURES + 1)) fi done BOOKINGS_RESPONSE=$(curl -s -H "Authorization: Bearer $TOKEN" \ "https://api.navan.com/v1/bookings?page=0&size=1") for FIELD in uuid; do HAS_FIELD=$(echo "$BOOKINGS_RESPONSE" | jq ".data[0] | has(\"$FIELD\")") if [ "$HAS_FIELD" != "true" ]; then echo "REGRESSION: /v1/bookings missing required field: $FIELD" FAILURES=$((FAILURES + 1)) fi done echo "Regression result: $FAILURES failures" [ "$FAILURES" -gt 0 ] && exit 1
Step 7 — Change Response Playbook
When a schema change is detected:
| Change Type | Severity | Response |
|---|---|---|
| New field added | Low | Log it, update baseline, no code change needed |
| Field renamed | High | Add new name as fallback, deploy behind flag |
| Field removed | Critical | Identify impact, implement fallback, alert team |
| Type changed (string to int) | High | Update parser, add type coercion |
| Endpoint URL changed | Critical | Update client config, monitor old URL for redirect |
| Auth flow changed | Critical | Immediate attention — test |
Output
- Baseline schema snapshots stored in version control
- Drift detection script running on a schedule (cron or CI)
- Defensive parsing patterns applied to all API response handlers
- Feature flag configuration for gradual rollout of schema changes
- Regression test suite covering critical field presence
Error Handling
| Issue | Detection | Response |
|---|---|---|
| New unknown fields in response | Drift detection script | Log, update baseline, no action unless field replaces existing |
| Required field missing | Regression test failure | Roll back to cached data, alert team, open support ticket |
| Response type changed | jq parse error | Add type checking, coerce if possible, alert if not |
| Endpoint returns 404 | Health check failure | Check for URL changes, contact Navan support |
| Auth endpoint behavior change | Token acquisition failure | Test manually, check Admin > Integrations |
Examples
Quick schema health check:
# One-liner: check if API response structure matches expectations TOKEN=$(curl -s -X POST "https://api.navan.com/ta-auth/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=$NAVAN_CLIENT_ID&client_secret=$NAVAN_CLIENT_SECRET" \ | jq -r '.access_token') echo "Users fields: $(curl -s -H "Authorization: Bearer $TOKEN" \ "https://api.navan.com/v1/users" | jq '.data[0] | keys | length') keys" echo "Bookings fields: $(curl -s -H "Authorization: Bearer $TOKEN" \ "https://api.navan.com/v1/bookings?page=0&size=1" | jq '.data[0] | keys | length') keys"
Resources
- Navan Help Center — API documentation and change notices
- Navan Security — Infrastructure and TLS/encryption details
- Navan Integrations — Connector ecosystem and partner updates
Next Steps
- Use
to capture current API state as a baselinenavan-debug-bundle - Use
to verify production hardening after changesnavan-prod-checklist - Use
to add regression tests to your CI pipelinenavan-ci-integration