Developer API · v1

Your food log, scriptable.

The Countwell API gives you programmatic, read access to your own food log: every entry, fully detailed, plus daily totals and goals. Pull it into a home dashboard, a spreadsheet, a Raycast script, or whatever you are building next. It is part of Countwell Premium.

Quickstart

  1. 1. Create a key. Go to your account page and create an API key in the Developer section. It looks like cw_live_… and is shown exactly once.
  2. 2. Call the API. Send the key as a Bearer token:
curl https://countwell.app/api/v1/me \
  -H "Authorization: Bearer cw_live_your_key_here"

3. Pull your log. Fetch today with GET /days/today, a window with GET /entries?from=…&to=…, or stay in sync forever with delta sync.

Authentication

Every request needs a personal API key in the Authorization header: Authorization: Bearer cw_live_…. Keys are created and revoked on your account page (sign in with the same account you use in the app), and each key is bound to your user: it can only ever read your own data.

We store a hash of the key, never the key itself, so it cannot be recovered later. Treat keys like passwords: keep them out of client-side code and public repos. You can hold up to 10 active keys, and revoking one cuts access immediately. Keys carry the nutrition:read scope. v1 is read only; write scopes are planned.

An active Countwell Premium subscription is required. If the subscription lapses, requests return 402 pro_required until it is renewed. Your keys and your data stay put.

Rate limits

Each key can make 100 requests per minute and 10,000 requests per day. Every response reports where you stand:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute (100)
X-RateLimit-RemainingRequests left in the current minute
X-RateLimit-ResetUnix time the minute window resets
X-RateLimit-Day-LimitRequests allowed per day (10,000)
X-RateLimit-Day-RemainingRequests left today
X-RateLimit-Day-ResetUnix time the day window resets

Exceeding either window returns 429 rate_limited with a Retry-After header (seconds). Polling once a minute with delta sync stays comfortably inside both limits.

Errors

Errors share one envelope: { "error": "code", "message": "…" }.

StatusCodeWhen
400invalid_requestMalformed dates, cursors, or parameters
401unauthorizedMissing, invalid, or revoked key
402pro_requiredNo active Premium subscription
403insufficient_scopeKey lacks a required scope
429rate_limitedRate limit exceeded; see Retry-After
500server_errorSomething failed on our side
GET

/me

A connectivity check: who this key belongs to and what it can do. Useful as the first call in any integration.

Request
curl https://countwell.app/api/v1/me \
  -H "Authorization: Bearer $COUNTWELL_KEY"
Response
{
  "user": {
    "id": "clx0k2…",
    "email": "you@example.com",
    "displayName": "Dylan",
    "timezone": "America/New_York"
  },
  "key": {
    "id": "cmck91…",
    "name": "Home dashboard",
    "prefix": "cw_live_a1b2c3",
    "scopes": ["nutrition:read"],
    "createdAt": "2026-07-16T14:03:00.000Z",
    "lastUsedAt": "2026-07-16T18:20:41.000Z"
  },
  "plan": "premium"
}
GET

/days/{date}

One calendar day in full: every live entry plus totals, the active goal, and what remains. {date} is YYYY-MM-DD in your account timezone, or the literal today.

Request
curl https://countwell.app/api/v1/days/2026-07-16 \
  -H "Authorization: Bearer $COUNTWELL_KEY"
Response
{
  "date": "2026-07-16",
  "timezone": "America/New_York",
  "entries": [ { …entry } ],
  "totals": {
    "kcal": 1620.5, "proteinG": 128.2, "carbsG": 141.0, "fatG": 58.9,
    "satFatG": 14.1, "fiberG": 27.3, "sugarG": 31.2, "sodiumMg": 2410.8
  },
  "goal": { "date": "2026-07-16", "calories": 2100, "proteinG": 140, "carbsG": 210, "fatG": 70 },
  "remaining": { "calories": 479.5, "proteinG": 11.8, "carbsG": 69.0, "fatG": 11.1 }
}
GET

/entries

The food log itself. With no parameters it returns your most recent entries, newest first. Pass a date window for history, or updatedSince for delta sync.

ParameterDescription
from, toInclusive YYYY-MM-DD window (366 days max), oldest first
updatedSinceISO-8601 timestamp; switches to delta sync (see below)
limitPage size, 1 to 500. Default 100
cursorOpaque pagination cursor from a previous response
Request
curl "https://countwell.app/api/v1/entries?from=2026-07-01&to=2026-07-16&limit=200" \
  -H "Authorization: Bearer $COUNTWELL_KEY"
Response
{
  "entries": [ { …entry }, … ],
  "nextCursor": "eyJ0IjoiMjAyNi0wNy0x…",
  "hasMore": true
}

When hasMore is true, pass nextCursor back as ?cursor= to fetch the next page. Cursors are stable: pages never skip or repeat an entry, even when entries share a timestamp.

Delta sync

The pattern the API is built around: instead of re-downloading history, ask for everything that changed since your last pull. Deleted entries come back as tombstones (deletedAt set) so your copy can propagate deletes too. This is how the iOS app stays in sync, exposed for your projects.

# First pull: everything (updatedSince far in the past)
curl "https://countwell.app/api/v1/entries?updatedSince=1970-01-01T00:00:00Z" \
  -H "Authorization: Bearer $COUNTWELL_KEY"

# Then remember the updatedAt of the last entry you saw, and poll:
curl "https://countwell.app/api/v1/entries?updatedSince=2026-07-16T18:20:41.000Z" \
  -H "Authorization: Bearer $COUNTWELL_KEY"

Page with cursor until hasMore is false, upsert rows by id, delete rows where deletedAt is set, and store the last updatedAt as your next updatedSince. A once-a-minute poll uses 1,440 of your 10,000 daily requests.

The entry object

Entries capture nutrition at log time, so history never shifts under you. Identity and provenance fields tell you where the food data came from.

{
  "id": "0d9f0a4e-6f2a-4c1e-9df3-1a2b3c4d5e6f",
  "localDate": "2026-07-16",
  "mealType": "LUNCH",
  "loggedAt": "2026-07-16T16:42:10.000Z",
  "source": "FATSECRET",
  "fatsecretFoodId": "4278256",
  "fatsecretServingId": "4585216",
  "usdaFdcId": null,
  "sourceFetchedAt": "2026-07-16T16:42:08.000Z",
  "copiedFromEntryId": null,
  "foodName": "Chicken Burrito Bowl",
  "brand": "Chipotle Mexican Grill",
  "quantity": 1,
  "unit": "bowl",
  "servingGrams": 630,
  "nutrition": {
    "kcal": 705, "proteinG": 52.4, "carbsG": 68.1, "fatG": 24.9,
    "satFatG": 7.2, "fiberG": 12.5, "sugarG": 4.8, "sodiumMg": 1480.2,
    "micros": { "potassiumMg": 1240 }
  },
  "components": [],
  "updatedAt": "2026-07-16T16:42:10.000Z",
  "deletedAt": null
}

components breaks aggregate meals (like an AI photo scan) into their source foods. micros carries long-tail micronutrients when the source provides them. Nutrition fields are null when unknown, never zero-filled.

Prefer natural language?

Countwell is also an MCP connector: point Claude, ChatGPT, or any MCP-capable app at your account and it can read your journal and log meals conversationally. The API and the connector share the same data and the same Premium plan. Set it up from your account page.