terra-troubleshooting
Terra Troubleshooting
Diagnose and resolve common Terra API issues.
Quick Diagnostics
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))
Common Issues
Authentication Issues
"Invalid API key or dev-id"
Cause: Incorrect credentials or wrong environment.
Solution:
# Check you're using correct environment credentials
ENVIRONMENTS = {
"testing": {
"dev_id": "botaniqalmedtech-testing-SjyfjtG33s",
"api_key": "_W7Pm-kAaIf1GA_Se21NnzCaFZjg3Izc"
},
"staging": {
"dev_id": "botaniqalmedtech-staging-uGKHOPRCi1",
"api_key": "fJia30spa-EqB_CMqZBzzrkMWZ_u2pv_"
},
"production": {
"dev_id": "botaniqalmedtech-prod-yrc99AwsT2",
"api_key": "Il4YUTc-e00EcvKGUx63j7YE3MJ-7QIz"
}
}
# Verify connection
from terra import Terra
client = Terra(**ENVIRONMENTS["testing"])
print(client.integrations.fetch())
Widget session expired
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
Mobile SDK token invalid
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
Connection Issues
User stuck on "connecting"
Cause: OAuth flow interrupted or WebView used.
Solution:
- Never use WebView/iFrame for OAuth
- Open auth URL in real browser
- Handle redirects properly:
// 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);
Provider returns "Access Denied"
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
WHOOP / Dexcom connection fails
Cause: These require special activation.
Solution: Contact Terra support at [email protected] for:
- WHOOP access activation
- Dexcom (CGM) access activation
- Freestyle Libre EU dedicated API keys
- Strava dedicated API keys
Data Sync Issues
No data received
Causes:
- User just connected (data takes time to sync)
- User has no data in that date range
- Provider is "polled" type (5-min delay)
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"))
Duplicate data received
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}
)
Historical data missing
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 |
Webhook Issues
Webhooks not received
Checklist:
-
Check webhook configured in dashboard:
- Terra Dashboard → Destinations → Webhooks
- Verify URL is correct and HTTPS
-
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
Signature verification failing
Causes:
- Wrong signing secret
- Body modified before verification
- Incorrect signature algorithm
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.
Webhook timeouts
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)
SDK Issues
iOS: Apple Health no data
Causes:
- Permissions not granted
- Background delivery not enabled
- App not authorized in Health app
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
Android: Samsung Health fails
Causes:
- Samsung Health app not installed
- minSDK < 28
- Missing permissions
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"/>
Android: Health Connect issues
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)
}
React Native: Build failures
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
Provider-Specific Issues
MyFitnessPal: Gateway timeout
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
Garmin: Data delayed
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
Fitbit: Token revoked
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)
Debug Mode
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
Support Escalation
If issues persist:
- Check Terra Status: https://status.tryterra.co
- Documentation: https://docs.tryterra.co
- Email Support: [email protected]
- Include in support request:
- Dev ID (not API key!)
- User ID (if applicable)
- Error message
- Timestamp of issue
- Provider affected
Health Check Script
#!/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
envs = {
"testing": ("botaniqalmedtech-testing-SjyfjtG33s", "_W7Pm-kAaIf1GA_Se21NnzCaFZjg3Izc"),
"staging": ("botaniqalmedtech-staging-uGKHOPRCi1", "fJia30spa-EqB_CMqZBzzrkMWZ_u2pv_"),
"production": ("botaniqalmedtech-prod-yrc99AwsT2", "Il4YUTc-e00EcvKGUx63j7YE3MJ-7QIz"),
}
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()
Related Skills
- terra-auth: Credential management
- terra-connections: Connection flows
- terra-webhooks: Webhook handling
- terra-data: Data retrieval
- terra-sdk: SDK integration