{"openapi":"3.1.0","info":{"title":"Food Diary API","version":"0.1.0","description":"Per-user meal-logging and goal-tracking API for the Food Diary PWA. Sessions are issued by WorkOS AuthKit (sealed `fd_session` cookie); agents authenticate via the OIDC flow advertised at /.well-known/oauth-authorization-server.","contact":{"name":"Food Diary","url":"https://trackwhatyoueat.com"},"license":{"name":"UNLICENSED"}},"servers":[{"url":"https://api.trackwhatyoueat.com"}],"tags":[{"name":"meals","description":"Logging + deletion of meal entries and items."},{"name":"days","description":"Per-day nutrient totals and day-type resolution."},{"name":"goals","description":"Goal targets and progress."},{"name":"users","description":"Identity, weekday→day-type map, data export / deletion."},{"name":"health","description":"Service health probes."},{"name":"discovery","description":"Agent-readiness discovery endpoints."},{"name":"auth","description":"Authentication: cookie + RFC 8628 device-flow surfaces."},{"name":"foods","description":"Food cache: search, common, direct add."}],"components":{"schemas":{"DaySummary":{"type":"object","required":["date","dayType","dayTypeOverridden","totals","mealCount"],"properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"dayType":{"type":"string","enum":["strength","cardio","rest"]},"dayTypeOverridden":{"type":"boolean"},"totals":{"$ref":"#/components/schemas/FoodItemNutrients"},"mealCount":{"type":"integer","minimum":0}}},"FoodItemNutrients":{"type":"object","description":"Nutrient values keyed by the Nutrient enum (DESIGN §3).","additionalProperties":{"type":"number","minimum":0}},"GoalProgress":{"type":"object","required":["nutrient","unit","current","minTarget","maxTarget","status"],"properties":{"nutrient":{"type":"string"},"unit":{"type":"string"},"current":{"type":"number"},"minTarget":{"type":["number","null"]},"maxTarget":{"type":["number","null"]},"status":{"type":"string","enum":["within","approaching","exceeded","unset"]}}},"Problem":{"type":"object","description":"RFC 9457 Problem Details for HTTP APIs. Served with `application/problem+json`. The `code` extension is a stable snake_case identifier the twye CLI maps to exit codes.","required":["type","title","status","code"],"properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"code":{"type":"string","enum":["invalid_argument","unauthenticated","token_expired","forbidden","not_found","conflict","cost_cap_exceeded","rate_limited","transient","auth_not_configured","unknown"]},"retryable":{"type":"boolean"},"hint":{"type":"string"},"suggested_actions":{"type":"array","items":{"type":"string"}},"errors":{"type":"array","items":{"type":"object","required":["path","message"],"properties":{"path":{"type":"string"},"message":{"type":"string"}}}}}}},"securitySchemes":{"workosAuthKit":{"type":"oauth2","description":"WorkOS AuthKit OIDC. See /.well-known/oauth-authorization-server.","flows":{"authorizationCode":{"authorizationUrl":"https://fine-grass-81.authkit.app/oauth2/authorize","tokenUrl":"https://auth.workos.com/user_management/authenticate","scopes":{"openid":"","email":"","profile":"","offline_access":""}}}},"sessionCookie":{"type":"apiKey","in":"cookie","name":"fd_session","description":"Sealed AuthKit session cookie set on /auth/callback. Browsers send it automatically when fetch is called with credentials: \"include\"."}}},"security":[{"workosAuthKit":["openid","email"]},{"sessionCookie":[]}],"paths":{"/health":{"get":{"tags":["health"],"operationId":"getHealth","summary":"Service health probe (unauthenticated).","security":[],"responses":{"200":{"description":"Healthy"},"503":{"description":"Degraded"}}}},"/v1/days/{date}":{"get":{"tags":["days"],"operationId":"getDaySummary","summary":"DaySummary for the current user + date (YYYY-MM-DD in user TZ).","parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"DaySummary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DaySummary"}}}},"400":{"description":"Malformed date","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthenticated"},"404":{"description":"User not found"}}}},"/v1/goals/{date}":{"get":{"tags":["goals"],"operationId":"getGoalProgress","summary":"Per-nutrient GoalProgress[] for the date, honoring day type.","parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Progress","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GoalProgress"}}}}},"400":{"description":"Malformed date"},"401":{"description":"Unauthenticated"}}}},"/v1/meals/{id}":{"delete":{"tags":["meals"],"operationId":"deleteMeal","summary":"Delete a meal entry and its items.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Unauthenticated"},"404":{"description":"Meal not found"}}}},"/v1/meals/{mealId}/items/{itemId}":{"delete":{"tags":["meals"],"operationId":"deleteMealItem","summary":"Delete a single item within a meal entry.","parameters":[{"name":"mealId","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Unauthenticated"},"404":{"description":"Not found"}}}},"/v1/food-overrides":{"get":{"tags":["meals"],"operationId":"listFoodOverrides","summary":"List the caller's per-food nutrient overrides.","responses":{"200":{"description":"OK"},"401":{"description":"Unauthenticated"}}}},"/v1/food-overrides/{foodItemId}":{"post":{"tags":["meals"],"operationId":"upsertFoodOverride","summary":"Override a nutrient value on a cached food item for the caller.","parameters":[{"name":"foodItemId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nutrient","amount","unit","perQty","perUnit"],"properties":{"nutrient":{"type":"string"},"amount":{"type":"number"},"unit":{"type":"string"},"perQty":{"type":"number","exclusiveMinimum":0},"perUnit":{"type":"string"}}}}}},"responses":{"201":{"description":"Created"},"400":{"description":"Invalid body"}}}},"/v1/food-overrides/{foodItemId}/{nutrient}":{"delete":{"tags":["meals"],"operationId":"deleteFoodOverride","parameters":[{"name":"foodItemId","in":"path","required":true,"schema":{"type":"string"}},{"name":"nutrient","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed"}}}},"/v1/users/me/weekday-map":{"put":{"tags":["users"],"operationId":"putWeekdayMap","summary":"Set or clear the caller's weekday→day-type map (null = system default).","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":{"type":"string","enum":["strength","cardio","rest"]}}]}}}},"responses":{"200":{"description":"OK"},"400":{"description":"Invalid body"}}}},"/v1/day-type-overrides":{"post":{"tags":["days"],"operationId":"upsertDayTypeOverride","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date","dayType"],"properties":{"date":{"type":"string"},"dayType":{"type":"string","enum":["strength","cardio","rest"]}}}}}},"responses":{"201":{"description":"OK"},"400":{"description":"Invalid body"}}}},"/v1/day-type-overrides/{date}":{"delete":{"tags":["days"],"operationId":"deleteDayTypeOverride","parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"}}}},"/v1/users/me/export":{"get":{"tags":["users"],"operationId":"exportUserData","summary":"Return the caller's full data envelope (DESIGN §8 data retention).","responses":{"200":{"description":"OK"},"401":{"description":"Unauthenticated"}}}},"/v1/users/me":{"delete":{"tags":["users"],"operationId":"deleteSelf","summary":"Cascade-delete every per-user row (keeps global food cache).","responses":{"204":{"description":"Deleted"},"404":{"description":"User not found"}}}},"/auth/device/code":{"post":{"tags":["auth"],"operationId":"authDeviceCode","summary":"RFC 8628 §3.2 — issue device_code + user_code for CLI auth.","security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string"}}}}}},"responses":{"200":{"description":"Device authorization payload.","content":{"application/json":{"schema":{"type":"object","required":["device_code","user_code","verification_uri","expires_in","interval"],"properties":{"device_code":{"type":"string"},"user_code":{"type":"string"},"verification_uri":{"type":"string","format":"uri"},"verification_uri_complete":{"type":"string","format":"uri"},"expires_in":{"type":"integer"},"interval":{"type":"integer"}}}}}}}}},"/auth/device/token":{"post":{"tags":["auth"],"operationId":"authDeviceToken","summary":"RFC 8628 §3.4 — poll for the access token.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["grant_type","device_code"],"properties":{"grant_type":{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]},"device_code":{"type":"string"},"client_id":{"type":"string"}}}}}},"responses":{"200":{"description":"Access + refresh tokens.","content":{"application/json":{"schema":{"type":"object","required":["access_token","refresh_token","expires_in","token_type","user_id"],"properties":{"access_token":{"type":"string"},"refresh_token":{"type":"string"},"expires_in":{"type":"integer"},"token_type":{"type":"string","enum":["Bearer"]},"user_id":{"type":"string"},"email":{"type":"string","format":"email"}}}}}},"400":{"description":"RFC 8628 polling error. `error` is one of: authorization_pending, slow_down, expired_token, access_denied.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","enum":["authorization_pending","slow_down","expired_token","access_denied","invalid_request"]}}}}}}}}},"/auth/device":{"get":{"tags":["auth"],"operationId":"authDevicePage","summary":"User-facing approval page for the CLI device-flow.","security":[],"parameters":[{"name":"user_code","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"HTML approval page."},"302":{"description":"Redirect to /auth/login (browser session required)."}}}},"/auth/refresh":{"post":{"tags":["auth"],"operationId":"authRefresh","summary":"CLI silent refresh — rotates the refresh_token on each call.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["refresh_token"],"properties":{"refresh_token":{"type":"string"},"client_id":{"type":"string"}}}}}},"responses":{"200":{"description":"New token pair.","content":{"application/json":{"schema":{"type":"object","required":["access_token","refresh_token","expires_in","token_type"],"properties":{"access_token":{"type":"string"},"refresh_token":{"type":"string"},"expires_in":{"type":"integer"},"token_type":{"type":"string"},"user_id":{"type":"string"},"email":{"type":"string"}}}}}},"401":{"description":"Token expired or invalid (RFC 9457 Problem).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/foods/{id}":{"get":{"tags":["foods"],"operationId":"getFoodById","summary":"Return a single food item, its nutrient profile, and known aliases.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Food item with nutrients + aliases.","content":{"application/json":{"schema":{"type":"object","required":["id","canonicalName","nutrients","aliases"],"properties":{"id":{"type":"string"},"canonicalName":{"type":"string"},"normalizedKey":{"type":"string"},"defaultUnit":{"type":"string"},"source":{"type":"string"},"nutrients":{"type":"array","items":{"type":"object","required":["nutrient","amount","unit","perQty","perUnit"],"properties":{"nutrient":{"type":"string"},"amount":{"type":"number"},"unit":{"type":"string"},"perQty":{"type":"number"},"perUnit":{"type":"string"}}}},"aliases":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Unauthenticated.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No food item with that id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/foods":{"post":{"tags":["foods"],"operationId":"addFood","summary":"Direct-add a food item. Idempotent on the derived normalized_key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["canonicalName","defaultUnit","nutrients"],"properties":{"canonicalName":{"type":"string"},"normalizedKey":{"type":"string"},"defaultUnit":{"type":"string"},"nutrients":{"type":"array","minItems":1,"items":{"type":"object","required":["nutrient","amount","perUnit"],"properties":{"nutrient":{"type":"string"},"amount":{"type":"number","minimum":0},"perQty":{"type":"number","exclusiveMinimum":0},"perUnit":{"type":"string"}}}},"aliases":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Existing food item — body carries its id and `created: false`.","content":{"application/json":{"schema":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string"},"created":{"type":"boolean","enum":[false]}}}}}},"201":{"description":"New food item.","content":{"application/json":{"schema":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string"},"created":{"type":"boolean","enum":[true]}}}}}},"400":{"description":"Validation failure (RFC 9457 Problem).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthenticated.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}}}