B
BAD.Docs

BAD developer docs

Connect any agent or app to your training data. A full REST API, an MCP server with 26 tools, and badstack — a gstack-style, auto-updating skill/tool stack you install with one command. Built to be read by humans and agents alike.

BAD is the intelligence layer above your health data

BAD is not another wearable. It's the intelligence layer — the unified health graph — that sits above every wearable, lab, and health source and fuses them into one cross-source picture and one answer: “am I getting healthier, and what should I do this week?”

The API, MCP server, and badstackexist so any agent can read & write that one unified graph programmatically — activities, recovery, labs, nutrition, and body composition, merged best-of-source across providers rather than siloed per app.

Today the graph is populated by Apple Health (including Apple Watch via HealthKit) and Strava. It's designed to absorb every other source the same way — via OAuth/API adapters where they exist, or raw uploads (lab PDFs, DEXA reports, device screenshots) where they don't — each new source landing in the same canonical layers. Recovery wearables (Whoop, Oura, Garmin), biomarkers (Superpower, Function Health, TruDiagnostic), and body composition (DEXA, VO₂max) are on the roadmap via that adapter / upload pattern; they are not live yet.

Quickstart

Every request authenticates with a Bearer token. Grab a developer API key (it starts with bad_sk_) from Settings → API — that screen also hands you a ready-to-paste setup prompt for your agent. Then export it and make a call.

shell
export BAD_API_KEY="bad_sk_..."

# Verify the key — fetch your profile
curl "https://bad.app/api/v1/me" -H "Authorization: Bearer $BAD_API_KEY"

One-curl install (badstack)

The fastest path: install the badstack onto any local agent. It pulls down the skills + MCP wiring and keeps itself updated.

one-curl install
curl -fsSL https://bad.app/api/badstack/install | BAD_API_KEY=bad_sk_… sh

What is badstack?

badstack is a gstack-style skill and tool stack for any local coding/agent runtime. One install command drops in a curated set of BAD skills (slash commands, prompts) plus the MCP server wiring, so your agent immediately knows how to read your training data, log workouts and meals, pull your digest, and update your plan — without you hand-wiring anything.

  • Auto-updating — the stack refreshes its skills + tool definitions in the background, so new BAD tools show up without a re-install.
  • Agent-agnostic — works with Claude Code, Cursor, Codex, and any MCP-capable client.
  • Key-scoped — everything runs against your bad_sk_ key, so it only ever touches your own account.

Your Settings → API screen gives you both the key and a copy-paste prompt that tells your agent how to use the stack.

MCP setup — per agent

The BAD MCP server is plain Streamable HTTP at https://bad.app/api/mcp. Authenticate with the same bad_sk_ key via the Authorization header. Pick your agent below.

Claude Code

shell
claude mcp add --transport http bad https://bad.app/api/mcp \
  --header "Authorization: Bearer $BAD_API_KEY"

Cursor

Add to ~/.cursor/mcp.json:

~/.cursor/mcp.json
{
  "mcpServers": {
    "bad": {
      "url": "https://bad.app/api/mcp",
      "headers": {
        "Authorization": "Bearer bad_sk_..."
      }
    }
  }
}

Codex

Add to ~/.codex/config.toml:

~/.codex/config.toml
[mcp_servers.bad]
url = "https://bad.app/api/mcp"

[mcp_servers.bad.headers]
Authorization = "Bearer bad_sk_..."

Generic MCP client (Pi.dev / Hermes / OpenClaw / any)

Any MCP client that speaks Streamable HTTP can use this shape:

mcp.json
{
  "mcpServers": {
    "bad": {
      "type": "http",
      "url": "https://bad.app/api/mcp",
      "headers": {
        "Authorization": "Bearer bad_sk_..."
      }
    }
  }
}

Authentication

Both the REST API and the MCP server authenticate with a single header: Authorization: Bearer <token>. Two credential types are accepted:

  • Developer API key — starts with bad_sk_. Created in Settings → API. This is what agents and the badstack use.
  • Session token — issued to the web and iOS apps at login. Also works as a Bearer credential.

Keys are scoped to one athlete account with full read/write access. Only a SHA-256 hash and a short display prefix are stored server-side — the plaintext key is shown once, at creation. Keep it out of client-side code, and revoke it from Settings if it leaks.

invalid key — error shape
{ "error": { "code": "ERROR", "message": "Invalid or revoked API key" } }

MCP tools reference (26)

Every tool the BAD MCP server exposes. The user id is taken from your Bearer credential — you never pass it. * = required.

ToolPurposeParams
get_activitiesRecent workout activities.limit?
get_plan_itemsTraining plan items for a date.date?
get_training_digestStats, flags, and narrative summary.type? (7day|14day|30day|weekly)
get_user_profileAuthenticated user profile.
log_activityLog a manual workout.sportType*, startTime*, localDate*, title?, durationSec?, distanceMeters?
log_weightLog a body-weight reading.weight*, slot? (morning|evening|auto), date?
log_habitsLog daily habit completions.habits*, date?, notes?
log_dailyWrite a full daily tracker row (merges).date?, weight_in?, weight_out?, miles_run?, habits?, notes?
get_daily_logA day's log (weight slots, habits, miles).date?
get_daily_log_historyDaily-log history for trend analysis.days?
get_timelineUnified, deduped Strava + Apple Health timeline.limit?, since?
get_activity_detailFused detail: sources, splits, laps, streams.canonicalId*
log_mealLog a meal with optional macros.description*, mealType?, calories?, protein?, carbs?, fat?, fiber?, date?
get_nutritionA day's macro totals + meals.date?
log_measurementLog body tape-measure data (merges).date?, weight?, neck?, chest?, waist?, hips?, …
get_measurementsBody-measurement history (newest first).limit?
log_labLog a blood / biomarker result.displayName*, value*, unit?, date?, refLow?, refHigh?, optimalLow?, optimalHigh?, category?, notes?
get_labsLatest value per biomarker + trend.
log_checkinSubjective check-in (mood/energy/soreness/stress).date?, mood?, energy?, soreness?, stress?, notes?
log_recoveryRecovery metrics (sleep, HRV, RHR, readiness).date?, sleepMinutes?, hrv?, restingHeartRate?, readiness?
get_consistencyConsistency score across pillars.days?
get_best_effortsPer-distance running personal records.
get_body_fatLatest Navy body-fat estimate + history.limit?
log_body_fatLog a Navy body-fat estimate (computes %BF).biologicalSex*, heightInches*, neckInches*, waistInches*, hipInches?, weightLbs?, date?
get_memoriesDurable facts the coach remembers about you.
rememberSave a durable fact for the coach to remember.text*

List them live at any time:

tools/list (JSON-RPC)
curl "https://bad.app/api/mcp" -X POST \
  -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

REST reference

All endpoints live under https://bad.app/api/v1, return JSON, and (except the auth endpoints) require the Bearer header. Grouped logically below.

Responses are canonical — one record per real-world event/day/marker, merged best-of-source across every connected provider — so reads like /timeline and /activities/detail are richer than any single source and expose which sources contributed.

Activities & Timeline

Log workouts and read a unified, deduped Strava + Apple Health history with heatmap buckets and fused per-activity detail.

GET/v1/activitiesList activities, newest first (limit?, type?).
POST/v1/activitiesLog a manual activity.
GET/v1/activities/{id}A single activity by id.
GET/v1/activities/detailFused detail for a workout (sources, splits, streams).
GET/v1/activities/extrasSupplemental per-activity data.
GET/v1/timelineUnified deduped timeline + heatmap + source counts (limit?, since?).
GET/v1/best-effortsPer-distance running personal records.
GET/v1/sourcesConnected data sources.
DELETE/v1/sourcesRemove a connected data source.
Activities & Timeline — example
curl "https://bad.app/api/v1/timeline?limit=200" \
  -H "Authorization: Bearer $BAD_API_KEY"

Daily Logs & Tracker

Per-day weight slots, miles, habits, notes, subjective check-ins, the consistency score, and the reset journal.

GET/v1/daily-logsToday's log, ?date=, or ?startDate=&endDate= range.
PATCH/v1/daily-logsCreate / update a daily log (habits merge).
GET/v1/checkinSubjective check-in for a day.
POST/v1/checkinLog a subjective check-in (mood/energy/soreness/stress).
GET/v1/consistencyConsistency score across pillars (days?).
GET/v1/reset-journalConfession / reset journal entries (limit?).
Daily Logs & Tracker — example
curl "https://bad.app/api/v1/daily-logs" \
  -X PATCH -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "weight_in": 178.2, "habits": { "keto": true, "core_score": 4 } }'

Nutrition

Meal logging with optional AI-estimated macros and per-day totals.

GET/v1/nutritionA day's macro totals + meals (date?).
POST/v1/nutritionLog a meal with optional macros.
Nutrition — example
curl "https://bad.app/api/v1/nutrition" \
  -X POST -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "description": "2 eggs + oatmeal", "calories": 420, "protein": 24 }'

Health & Wellness

Navy body-fat estimates, body measurements, HealthKit sync, sync cursors, and Strava status.

GET/v1/body-fatLatest body-fat estimate + history (limit?).
POST/v1/body-fatLog a Navy body-fat estimate (computes %BF).
GET/v1/measurementsBody-measurement history.
POST/v1/measurementsLog body measurements (merges per day).
DELETE/v1/measurementsDelete a measurement row.
POST/v1/sync/healthkitAccept a HealthKit batch from iOS.
GET/v1/sync/statusPer-type HealthKit sync cursors.
GET/v1/strava/statusStrava connection + recent-activity count.
POST/v1/strava/disconnectDisconnect Strava.
Health & Wellness — example
curl "https://bad.app/api/v1/body-fat" \
  -X POST -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "biologicalSex": "male", "heightInches": 70, "neckInches": 15, "waistInches": 34, "weightLbs": 175 }'

Labs

Blood / biomarker results with reference + optimal ranges and trend series.

GET/v1/labsLatest value per biomarker + ranges + trend.
POST/v1/labsLog one biomarker result (upserts per marker/date).
Labs — example
curl "https://bad.app/api/v1/labs" \
  -X POST -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "displayName": "Total Cholesterol", "value": 168, "unit": "mg/dL", "category": "Lipids" }'

Coach & Chat

Stream the AI coach, read threads, training digest, plan items, coach + messaging settings, and durable coach memories.

POST/v1/chatSend a message to the AI coach (streamed response).
GET/v1/chatsList chat threads.
GET/v1/chats/{chatId}A single thread with messages.
GET/v1/digestStructured training digest (type?).
GET/v1/plan-itemsToday's plan items (date?).
PATCH/v1/plan-items/{itemId}Update / complete a plan item.
GET/v1/coach-settingsNudge prefs + messaging readiness flags.
PATCH/v1/coach-settingsUpdate nudge frequency + hour.
GET/v1/messaging-settingsRead 1:1 messaging prefs.
PATCH/v1/messaging-settingsUpdate phone, channel, opt-in.
POST/v1/messaging/sendSend a 1:1 coach message to your own phone.
POST/v1/verify-phoneVerify a phone number for messaging.
GET/v1/memoriesDurable facts the coach remembers.
POST/v1/memoriesAdd a coach memory.
DELETE/v1/memoriesForget a coach memory (?id=).
Coach & Chat — example
curl "https://bad.app/api/v1/chat" \
  -X POST -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" --no-buffer \
  -d '{ "message": "How should I pace my long run Sunday?" }'

Crews & Social

Activity feed, crews, follows / accountability, challenges, and the shareable public profile.

GET/v1/feedActivity feed posts.
POST/v1/feed/reactReact to a feed post.
GET/v1/crewsThe user's crews.
GET/v1/crews/{crewId}/feedA crew feed.
GET/v1/crews/{crewId}/leaderboardA crew leaderboard.
GET/v1/followsFollow counts + accountability partners.
POST/v1/followsFollow another athlete.
DELETE/v1/followsUnfollow an athlete.
GET/v1/challengesThe user's challenges.
GET/v1/public-profileOwn public profile settings.
POST/v1/public-profileCreate / update the public profile.
GET/v1/referralsReferral status + invites.
Crews & Social — example
curl "https://bad.app/api/v1/feed" \
  -H "Authorization: Bearer $BAD_API_KEY"

Profile & Growth

Profile, preferences, badges, locker artifacts, unified search, and account deletion.

GET/v1/meAuthenticated user profile.
PATCH/v1/profileUpdate displayName, timezone, units.
GET/v1/preferencesAgent preferences (tone, notes).
PATCH/v1/preferencesUpdate agent preferences.
GET/v1/badgesEarned + locked achievement badges.
GET/v1/lockerSaved artifacts (plans, notes, widgets).
PATCH/v1/locker/{id}Update a locker artifact.
DELETE/v1/locker/{id}Delete a locker artifact.
POST/v1/locker/reorderReorder locker artifacts.
GET/v1/searchUnified search across activities, artifacts, chats (q=).
DELETE/v1/accountCascade-delete the user and all data.
Profile & Growth — example
curl "https://bad.app/api/v1/me" \
  -H "Authorization: Bearer $BAD_API_KEY"

Screenshot Import

Import historical tracker rows (weight, miles, habits) from a screenshot via AI vision.

POST/v1/screenshotExtract + import tracker data from an image.
GET/v1/photosList uploaded photos.
POST/v1/photosUpload a photo.
DELETE/v1/photosDelete a photo.
Screenshot Import — example
curl "https://bad.app/api/v1/screenshot" \
  -X POST -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "image": "data:image/png;base64,..." }'

Developer / Keys

Create, list, and revoke scoped bad_sk_ API keys programmatically. The plaintext key is returned only once.

GET/v1/keysList API keys (no secrets).
POST/v1/keysCreate a key — returns the plaintext key ONCE.
DELETE/v1/keysRevoke a key (?id=).
Developer / Keys — example
curl "https://bad.app/api/v1/keys" \
  -X POST -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Claude Code" }'

Integrations & Auth

Account auth (email/password + Sign in with Apple) that mints the session tokens usable as a Bearer credential.

POST/v1/auth/registerCreate an account → { token, user }.
POST/v1/auth/loginExchange email + password → { token, user }.
POST/v1/auth/appleSign in with Apple → { token, user }.
POST/v1/auth/sessionValidate a session token → { user }.
Integrations & Auth — example
curl "https://bad.app/api/v1/auth/login" \
  -X POST -H "Content-Type: application/json" \
  -d '{ "email": "you@example.com", "password": "•••" }'

Code samples

cURL

bash
# Log a meal, then read today's macro totals
curl "https://bad.app/api/v1/nutrition" -X POST \
  -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "description": "grilled chicken + rice", "calories": 560, "protein": 48 }'

curl "https://bad.app/api/v1/nutrition" -H "Authorization: Bearer $BAD_API_KEY"

JavaScript (fetch)

js
const BASE = 'https://bad.app/api/v1'
const headers = {
  Authorization: `Bearer ${process.env.BAD_API_KEY}`,
  'Content-Type': 'application/json',
}

// Read the unified timeline
const res = await fetch(`${BASE}/timeline?limit=50`, { headers })
if (!res.ok) throw new Error(await res.text())
const { activities, heatmap, counts } = await res.json()
console.log(counts, activities.length)

MCP tools/call (JSON-RPC)

json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "id": 2,
  "params": {
    "name": "log_meal",
    "arguments": {
      "description": "2 eggs + oatmeal",
      "calories": 420,
      "protein": 24
    }
  }
}

Over HTTP, that's a single POST to the MCP endpoint:

bash
curl "https://bad.app/api/mcp" -X POST \
  -H "Authorization: Bearer $BAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"get_training_digest","arguments":{}}}'

Drop-in agent prompt

Paste this into your agent's system prompt or a project instruction file once the BAD MCP server is connected. (Settings → API generates a personalized version too.)

agent prompt
You have access to the BAD MCP server (the user's training + health data).
Use it proactively:

- Before advising on training, call get_training_digest and get_timeline to
  ground yourself in real data.
- When the user reports a workout, meal, weight, or how they feel, log it:
  log_activity / log_meal / log_weight / log_checkin / log_recovery.
- For nutrition or body-composition questions, read get_nutrition, get_body_fat,
  get_measurements, and get_labs first.
- Use get_consistency to gauge adherence before pushing or easing off.
- When you learn a durable fact about the user (injury, preference, goal),
  call remember so future sessions keep it.

Never ask the user for their user id — it comes from the API key. Prefer
get_timeline over get_activities for cross-source history.