.claude/skills/terra-troubleshooting/SKILL.md
Terra API troubleshooting and debugging. Use when experiencing connection issues, data sync problems, webhook failures, SDK errors, or provider-specific issues.
npx skillsauth add adaptationio/skrillz terra-troubleshootingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Diagnose and resolve common Terra API issues.
from terra import Terra
def check_terra_health(client: Terra) -> dict:
"""Run basic health checks."""
results = {}
# 1. API connectivity
try:
integrations = client.integrations.fetch()
results["api_connection"] = f"OK - {len(integrations.integrations)} providers"
except Exception as e:
results["api_connection"] = f"FAILED - {e}"
# 2. List connected users
try:
users = client.user.getsubscriptions()
results["connected_users"] = f"OK - {len(users.users)} users"
except Exception as e:
results["connected_users"] = f"FAILED - {e}"
return results
# Usage
client = Terra(dev_id="...", api_key="...")
print(check_terra_health(client))
Cause: Incorrect credentials or wrong environment.
Solution:
# Check you're using correct environment credentials
ENVIRONMENTS = {
"testing": {
"dev_id": os.environ.get("TERRA_DEV_ID_TESTING"),
"api_key": os.environ.get("TERRA_API_KEY_TESTING")
},
"staging": {
"dev_id": os.environ.get("TERRA_DEV_ID_STAGING"),
"api_key": os.environ.get("TERRA_API_KEY_STAGING")
},
"production": {
"dev_id": os.environ.get("TERRA_DEV_ID_PRODUCTION"),
"api_key": os.environ.get("TERRA_API_KEY_PRODUCTION")
}
}
# Verify connection
from terra import Terra
client = Terra(**ENVIRONMENTS["testing"])
print(client.integrations.fetch())
Cause: Session URLs expire after 15 minutes.
Solution: Generate a new widget session:
response = client.authentication.generatewidgetsession(
reference_id="user_123",
auth_success_redirect_url="https://app.example.com/success",
auth_failure_redirect_url="https://app.example.com/failure"
)
# Redirect user to response.url immediately
Cause: Token expired (3 min) or already used.
Solution: Generate fresh token for each connection attempt:
# Backend generates new token each time
token = client.authentication.generateauthtoken(reference_id="user_123")
# Send token.token to mobile app
# Token is one-time use - generate new one if connection fails
Cause: OAuth flow interrupted or WebView used.
Solution:
// Wrong - WebView blocks OAuth
<WebView source={{ uri: authUrl }} />
// Correct - Open in browser
import { Linking } from 'react-native';
Linking.openURL(authUrl);
// Or use InAppBrowser
import { InAppBrowser } from 'react-native-inappbrowser-reborn';
InAppBrowser.open(authUrl);
Cause: Provider-side issue or insufficient permissions.
Solution:
# Check user's granted scopes
user = client.user.getuser(user_id="terra_abc123")
print(f"Scopes: {user.user.scopes}")
# If missing scopes, user needs to reconnect and grant all permissions
Cause: These require special activation.
Solution: Contact Terra support at [email protected] for:
Causes:
Solution:
from datetime import datetime, timedelta
def check_user_data(client: Terra, user_id: str) -> dict:
"""Check if user has any data."""
end = datetime.now()
start = end - timedelta(days=7)
results = {}
# Check each data type
for data_type in ["daily", "activity", "sleep", "body"]:
try:
method = getattr(client, data_type)
response = method.get(user_id=user_id, start_date=start, end_date=end)
results[data_type] = len(response.data)
except Exception as e:
results[data_type] = f"Error: {e}"
return results
print(check_user_data(client, "terra_abc123"))
Cause: Daily/body data updates multiple times per day.
Solution: Use UPSERT pattern, not INSERT:
# Wrong - creates duplicates
db.insert(data)
# Correct - overwrites existing
db.upsert(
{"user_id": user_id, "date": date},
{"$set": data}
)
Cause: Provider limits historical access.
Reference - Maximum historical data per provider:
| Provider | Limit | |----------|-------| | Garmin | 5 years | | Fitbit | 10 years | | Oura | 3 years | | WHOOP | 2 years | | Polar | 30 days | | COROS | 3 months |
Checklist:
Check webhook configured in dashboard:
Check endpoint is accessible:
curl -X POST https://your-webhook-url.com/terra \
-H "Content-Type: application/json" \
-d '{"type": "test"}'
Check server logs for incoming requests
Verify IP not blocked - Terra IPs:
18.133.218.210, 18.169.82.189, 18.132.162.19,
18.130.218.186, 13.43.183.154, 3.11.208.36,
35.214.201.105, 35.214.230.71, 35.214.252.53, 35.214.229.114
Causes:
Solution:
import hmac
import hashlib
def debug_signature(header: str, body: bytes, secret: str):
"""Debug signature verification."""
# Parse header
parts = dict(p.split("=") for p in header.split(","))
print(f"Timestamp: {parts['t']}")
print(f"Received signature: {parts['v1']}")
# Compute expected
message = f"{parts['t']}.{body.decode()}"
expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
print(f"Expected signature: {expected}")
# Compare
match = hmac.compare_digest(expected, parts['v1'])
print(f"Match: {match}")
return match
# Use in webhook handler
@app.route("/webhook", methods=["POST"])
def webhook():
header = request.headers.get("terra-signature")
body = request.get_data() # Must be raw bytes, not parsed JSON!
if not debug_signature(header, body, SIGNING_SECRET):
return "Invalid signature", 401
# Now parse JSON
payload = request.get_json()
Critical: Get raw body BEFORE parsing JSON.
Cause: Processing takes too long (>5 seconds recommended).
Solution: Process asynchronously:
from celery import Celery
celery = Celery()
@app.route("/webhook", methods=["POST"])
def webhook():
# Verify signature
# Queue for async processing
process_webhook.delay(request.get_json())
# Respond immediately
return "OK", 200
@celery.task
def process_webhook(payload):
# Do heavy processing here
save_to_database(payload)
send_notifications(payload)
Causes:
Solution:
// 1. Check permissions in code
Terra.checkPermissions { granted in
if !granted {
// Request permissions again
Terra.requestPermissions()
}
}
// 2. Enable background delivery
func application(_ app: UIApplication, didFinishLaunchingWithOptions...) {
Terra.setUpBackgroundDelivery()
}
// 3. User must enable in Settings → Privacy → Health → Your App
Causes:
Solution:
// 1. Check Samsung Health installed
if (!Terra.isSamsungHealthAvailable(context)) {
// Prompt user to install Samsung Health
showInstallSamsungHealthDialog()
}
// 2. Verify minSDK in build.gradle
android {
defaultConfig {
minSdkVersion 28 // Required for Terra Android SDK
}
}
// 3. Request permissions
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="android.permission.BODY_SENSORS"/>
Cause: Health Connect not installed or configured.
Solution:
// Check Health Connect availability
if (!Terra.isHealthConnectAvailable(context)) {
// Prompt to install Health Connect
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata")
}
startActivity(intent)
}
Common fixes:
# iOS: Clean and rebuild
cd ios
pod deintegrate
pod install
cd ..
npx react-native run-ios
# Android: Clean build
cd android
./gradlew clean
cd ..
npx react-native run-android
Cause: MyFitnessPal API is slow/unreliable.
Solution: Retry with exponential backoff:
import time
def fetch_with_retry(client, user_id, start, end, max_retries=3):
for attempt in range(max_retries):
try:
return client.nutrition.get(user_id, start, end)
except Exception as e:
if "timeout" in str(e).lower() and attempt < max_retries - 1:
time.sleep(2 ** attempt) # 1, 2, 4 seconds
continue
raise
Cause: Garmin is a "polled" provider (~5 min sync).
Solution: Wait for webhook rather than polling API:
# Don't poll repeatedly
# Instead, wait for webhook notification
Cause: User revoked access in Fitbit app.
Solution: Handle access_revoked webhook:
def handle_access_revoked(payload):
user_id = payload["user"]["user_id"]
# Mark user as disconnected
db.terra_users.update_one(
{"terra_user_id": user_id},
{"$set": {"status": "access_revoked"}}
)
# Notify user to reconnect
send_reconnect_notification(user_id)
Enable detailed logging:
import logging
# Enable Terra SDK logging
logging.getLogger("terra").setLevel(logging.DEBUG)
# Or enable all HTTP logging
import http.client
http.client.HTTPConnection.debuglevel = 1
If issues persist:
#!/usr/bin/env python3
"""Terra API health check script."""
from terra import Terra
from datetime import datetime, timedelta
def run_health_check():
print("=" * 50)
print("Terra API Health Check")
print("=" * 50)
# Test each environment - credentials loaded from env vars
envs = {
"testing": (os.environ.get("TERRA_DEV_ID_TESTING"), os.environ.get("TERRA_API_KEY_TESTING")),
"staging": (os.environ.get("TERRA_DEV_ID_STAGING"), os.environ.get("TERRA_API_KEY_STAGING")),
"production": (os.environ.get("TERRA_DEV_ID_PRODUCTION"), os.environ.get("TERRA_API_KEY_PRODUCTION")),
}
for env_name, (dev_id, api_key) in envs.items():
print(f"\n{env_name.upper()}:")
try:
client = Terra(dev_id=dev_id, api_key=api_key)
# Check API
integrations = client.integrations.fetch()
print(f" ✅ API Connected - {len(integrations.integrations)} providers")
# Check users
users = client.user.getsubscriptions()
print(f" ✅ Users: {len(users.users)} connected")
except Exception as e:
print(f" ❌ Error: {e}")
print("\n" + "=" * 50)
print("Health check complete")
if __name__ == "__main__":
run_health_check()
development
Setup secure web-based terminal access to WSL2 from mobile/tablet via ttyd + ngrok/Cloudflare/Tailscale. One-command install, start, stop, status. Use when you need remote terminal access, web terminal, browser-based shell, or mobile access to WSL2 environment.
development
Complete development workflows where Claude writes the code while Gemini and Codex provide research, planning, reviews, and different perspectives. Claude remains the main developer. Use for complex projects requiring expert planning and multi-perspective reviews.
development
Systematic progress tracking for skill development. Manages task states (pending/in_progress/completed), updates in real-time, reports progress, identifies blockers, and maintains momentum. Use when tracking skill development, coordinating work, or reporting progress.
testing
Comprehensive testing workflow orchestrating functional testing, example validation, integration testing, and usability assessment. Sequential workflow for complete skill testing from examples through scenarios to integration validation. Use when conducting thorough testing, pre-deployment validation, ensuring skill functionality, or comprehensive quality checks.