{
  "openapi": "3.1.0",
  "info": {
    "title": "whatevr api",
    "version": "1.0.0",
    "description": "Personal-token access to your own whatevr data. Timestamps are BARE-LOCAL (YYYY-MM-DDThh:mm:ss, no zone); send logged_at yourself or pass tz_offset_min (JS getTimezoneOffset(): minutes behind UTC) so days resolve in your timezone. Rate limits: ~100 requests/min per token, ~15/min on /v1/ai/log (sharded counters — ceilings are approximate; 429 + Retry-After when exceeded). AI agents can also connect natively over MCP: POST /mcp (same tokens, same limits)."
  },
  "servers": [
    {
      "url": "https://grandiose-bear-171.convex.site"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "wtvr_live_… token from Settings → API Access"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request body or parameters",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or revoked token",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "API access disabled or insufficient scope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limited — honor the Retry-After header",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/ai/log": {
      "post": {
        "operationId": "aiLog",
        "summary": "Log anything in natural language (food, exercise, water, weight)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 2000,
                    "examples": [
                      "two eggs and toast, black coffee"
                    ]
                  },
                  "logged_at": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$"
                  },
                  "tz_offset_min": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Logged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "applied_day": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "title": {
                                "type": "string"
                              },
                              "category": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "calories": {
                                "type": "number"
                              },
                              "calories_burned": {
                                "type": "number"
                              }
                            }
                          }
                        },
                        "failed_count": {
                          "type": "number",
                          "description": "present when some parsed items failed to save"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/logs/exercise": {
      "post": {
        "operationId": "logExercise",
        "summary": "Log structured exercise",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "activity",
                  "duration_minutes",
                  "calories_burned"
                ],
                "properties": {
                  "activity": {
                    "type": "string"
                  },
                  "duration_minutes": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 1440
                  },
                  "calories_burned": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 20000
                  },
                  "logged_at": {
                    "type": "string"
                  },
                  "tz_offset_min": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Logged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/logs/weight": {
      "post": {
        "operationId": "logWeight",
        "summary": "Log a weigh-in (also updates current weight when newest)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_lb"
                ],
                "properties": {
                  "weight_lb": {
                    "type": "number",
                    "minimum": 20,
                    "maximum": 1500
                  },
                  "note": {
                    "type": "string"
                  },
                  "logged_at": {
                    "type": "string"
                  },
                  "tz_offset_min": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Logged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/water": {
      "post": {
        "operationId": "logWater",
        "summary": "Log water",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount_fl_oz": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 256
                  },
                  "glasses": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 32,
                    "description": "8 fl oz each; used when amount_fl_oz is absent"
                  },
                  "logged_at": {
                    "type": "string"
                  },
                  "tz_offset_min": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Logged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "glasses": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/logs/update": {
      "post": {
        "operationId": "updateLog",
        "summary": "Correct an existing food log (title, numbers, ingredients, meal, time) — requires a full-access token",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "log_id"
                ],
                "properties": {
                  "log_id": {
                    "type": "string",
                    "description": "From GET /v1/today or /v1/day: recent[].id (type \"food\")"
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "calories": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 20000
                  },
                  "protein_g": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 2000
                  },
                  "carbs_g": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 2000
                  },
                  "fat_g": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 2000
                  },
                  "fiber_g": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 2000
                  },
                  "sugar_g": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 2000
                  },
                  "sodium_mg": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "meal": {
                    "type": "string",
                    "enum": [
                      "breakfast",
                      "lunch",
                      "dinner",
                      "snack"
                    ]
                  },
                  "logged_time": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$",
                    "description": "HH:MM 24h; time-of-day only, the date never changes"
                  },
                  "ingredients": {
                    "type": "array",
                    "maxItems": 50,
                    "description": "REPLACES the full ingredient list and totals recompute from it — cannot be combined with top-level numeric fields",
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "calories"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "maxLength": 120
                        },
                        "amount": {
                          "type": "string",
                          "maxLength": 60
                        },
                        "calories": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 20000
                        },
                        "protein_g": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 2000
                        },
                        "carbs_g": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 2000
                        },
                        "fat_g": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 2000
                        },
                        "fiber_g": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 2000
                        },
                        "sugar_g": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 2000
                        },
                        "sodium_mg": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100000
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean"
                        },
                        "log_id": {
                          "type": "string"
                        },
                        "updated": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "the request fields that were accepted; when logged_time changes, meal may additionally re-infer from the new hour and is included here"
                        },
                        "meal": {
                          "type": "string",
                          "description": "the log's meal after the edit (breakfast/lunch/dinner/snack)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No food log with that id on this account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/today": {
      "get": {
        "operationId": "getToday",
        "summary": "Today's totals, targets, water, streak, and activity feed",
        "parameters": [
          {
            "name": "tz_offset_min",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The day",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No data yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/day": {
      "get": {
        "operationId": "getDay",
        "summary": "A specific day's data",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The day",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No data yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/progress": {
      "get": {
        "operationId": "getProgress",
        "summary": "Daily series + weight trend",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "number",
              "description": "1–730, or 0 for all-time",
              "default": 30
            }
          },
          {
            "name": "anchor",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "tz_offset_min",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Progress",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Who am I: user, profile, targets, derived metrics",
        "responses": {
          "200": {
            "description": "Profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  }
}