From navan-pack
Detects schema drift in unversioned Navan API using curl/jq baselines and bash comparisons; guides defensive parsing and rollout for production integrations.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin navan-packThis skill is limited to using the following tools:
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.
Diagnoses and fixes Navan API errors like 401 Unauthorized, 403 Forbidden, 429 Rate Limit using curl diagnostics, token refresh, and permission checks.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
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.
client_id, client_secret) stored in a secret managercurl, jq, and diff for schema comparisonStore 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
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
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:
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)
}'
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:
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
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 /ta-auth/oauth/token |
| 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 /ta-auth/oauth/token manually, check Admin > Integrations |
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"
navan-debug-bundle to capture current API state as a baselinenavan-prod-checklist to verify production hardening after changesnavan-ci-integration to add regression tests to your CI pipeline