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. 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. 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:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per minute (100) |
X-RateLimit-Remaining | Requests left in the current minute |
X-RateLimit-Reset | Unix time the minute window resets |
X-RateLimit-Day-Limit | Requests allowed per day (10,000) |
X-RateLimit-Day-Remaining | Requests left today |
X-RateLimit-Day-Reset | Unix 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": "…" }.
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | Malformed dates, cursors, or parameters |
| 401 | unauthorized | Missing, invalid, or revoked key |
| 402 | pro_required | No active Premium subscription |
| 403 | insufficient_scope | Key lacks a required scope |
| 429 | rate_limited | Rate limit exceeded; see Retry-After |
| 500 | server_error | Something failed on our side |
/me
A connectivity check: who this key belongs to and what it can do. Useful as the first call in any integration.
curl https://countwell.app/api/v1/me \
-H "Authorization: Bearer $COUNTWELL_KEY"{
"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"
}/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.
curl https://countwell.app/api/v1/days/2026-07-16 \
-H "Authorization: Bearer $COUNTWELL_KEY"{
"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 }
}/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.
| Parameter | Description |
|---|---|
| from, to | Inclusive YYYY-MM-DD window (366 days max), oldest first |
| updatedSince | ISO-8601 timestamp; switches to delta sync (see below) |
| limit | Page size, 1 to 500. Default 100 |
| cursor | Opaque pagination cursor from a previous response |
curl "https://countwell.app/api/v1/entries?from=2026-07-01&to=2026-07-16&limit=200" \
-H "Authorization: Bearer $COUNTWELL_KEY"{
"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.