openapi: 3.0.3
info:
  title: Eat This Much API
  version: "2.0"
  description: |
    API endpoints for Eat This Much meal planning service.
    Generation, rendering, grocery, and food endpoints require API key authentication.
    API key and usage-management endpoints can also use a logged-in Django session cookie.
  contact:
    name: API Support
    email: support@eatthismuch.com

servers:
  - url: /
    description: API server

security:
  - ApiKeyAuth: [ ]
  - BearerAuth: [ ]

tags:
  - name: Keys
    description: API key management endpoints
  - name: Usage
    description: API usage and billing endpoints
  - name: Generator
    description: Meal plan generation endpoints
  - name: Render
    description: Render existing or edited meal plans to HTML or PDF from JSON output
  - name: Groceries
    description: Build grocery lists from meal plan data
  - name: Food
    description: Food information endpoints

paths:
  /gapi/generate/week/:
    post:
      operationId: generateWeek
      summary: Generate week meal plan
      description: |
        Generates a full week meal plan. Requires calories, number of meals, and diet preference.
        Limited to 3 concurrent requests per API user.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 50
      tags:
        - Generator
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
      parameters:
        - name: output
          in: query
          required: false
          description: Response output format. May be provided as a query param or POST param.
          schema:
            type: string
            enum: [json, html, pdf]
            default: json
        - name: format
          in: query
          required: false
          description: Template format for HTML or PDF output. May be provided as a query param or POST param.
          schema:
            type: string
            enum: [standard, extra_nutrition, condensed, standard_v1, plaintext, free]
            default: standard
      requestBody:
        required: true
        description: Week generation request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WeekGenerateRequest'
      responses:
        '200':
          description: Week plan generated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/WeekResponse'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
                    description: |
                      When `include_groceries` is specified on the request, the response includes:

                      ```json
                      "meta": {
                        "groceries": []
                      }
                      ```
                    example:
                      groceries:
                        - food_id: 2021
                          usages:
                            - food_name: "Stir-Fried Carrots and Mushrooms"
                              meal_name: "Lunch"
                              food_id: 5056799
                              date: "2026-03-04"
                              ingredient_display_amount: "1 1/2"
                              ingredient_units: "lb"
                            - food_name: "Stir-Fried Carrots and Mushrooms"
                              meal_name: "Lunch"
                              food_id: 5056799
                              date: "2026-03-09"
                              ingredient_display_amount: "1 1/2"
                              ingredient_units: "lb"
                            - food_name: "Stir-Fried Asparagus and Mushrooms"
                              meal_name: "Lunch"
                              food_id: 5056865
                              date: "2026-03-07"
                              ingredient_display_amount: "13 1/2"
                              ingredient_units: "large"
                          units: "cup, whole"
                          amount_to_buy: "17 1/2"
                          food_name: "White mushrooms"
                          food_group_id: 1100
            text/html:
              schema:
                type: string
            application/pdf:
              schema:
                type: string
                format: binary
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '429':
          description: Too many concurrent week generation requests for this API user
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string

  /gapi/generate/day/:
    post:
      operationId: generateDay
      summary: Generate day meal plan
      description: |
        Generates a single day meal plan. Requires calories, number of meals, and diet preference.
        Limited to 10 concurrent requests per API user.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 10
      tags:
        - Generator
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
      requestBody:
        required: true
        description: Day generation request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DayGenerateRequest'
      responses:
        '200':
          description: Day plan generated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/DayResponse'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '429':
          description: Too many concurrent day generation requests for this API user
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string

  /gapi/generate/meal/:
    post:
      operationId: generateMeal
      summary: Generate meal
      description: |
        Generates a single meal. Requires calories and diet preference.
        Limited to 20 concurrent requests per API user.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 5
      tags:
        - Generator
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
      requestBody:
        required: true
        description: Meal generation request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MealGenerateRequest'
      responses:
        '200':
          description: Meal generated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/MealResponse'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '429':
          description: Too many concurrent meal generation requests for this API user
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string

  /gapi/generate/foodobject/:
    post:
      operationId: generateFoodObject
      summary: Generate food object
      description: |
        Generates a single food object. Requires calories and diet preference.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 5
      tags:
        - Generator
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
      requestBody:
        required: true
        description: Food object generation request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FoodObjectGenerateRequest'
      responses:
        '200':
          description: Food object generated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/FoodObjectResponse'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'

  /gapi/render-plan/:
    post:
      operationId: renderPlan
      summary: Render week plan to HTML or PDF
      description: |
        Takes the JSON output from `/gapi/generate/week/` and renders it as HTML or PDF.

        Use the `output` query or POST parameter to choose the format:
        - `html` (default) - Formatted HTML document
        - `pdf` - PDF document

        This endpoint is useful when you want to display or print a previously generated
        or edited meal plan, or when you want to control the rendering options separately
        from generation.

        The endpoint reconstructs full food data from the database using the `food_id` values
        in the input, so the rendered output includes complete recipe and nutritional information.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 10
      tags:
        - Render
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
      parameters:
        - name: output
          in: query
          required: false
          description: Response output format. May be provided as a query param or POST param.
          schema:
            type: string
            enum: [ html, pdf ]
            default: html
        - name: format
          in: query
          required: false
          description: Template format for the rendered output. May be provided as a query param or POST param.
          schema:
            type: string
            enum: [ standard, extra_nutrition, condensed, plaintext ]
            default: standard
      requestBody:
        required: true
        description: Render request with week data and options
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RenderRequest'
            example:
              week_data:
                - day:
                    weekday: 1
                    meals:
                      - meal_title: "Breakfast"
                        foods:
                          - food_id: 1234
                            food_name: "Scrambled Eggs"
                            amount: 2
                            units: "large"
                      - meal_title: "Lunch"
                        foods:
                          - food_id: 5678
                            food_name: "Grilled Chicken Salad"
                            amount: 1
                            units: "serving"
                - day:
                    weekday: 2
                    meals:
                      - meal_title: "Breakfast"
                        foods:
                          - food_id: 33511
                            food_name: "Banana Oatmeal"
                            amount: 1
                            units: "bowl"
                      - meal_title: "Lunch"
                        foods:
                          - food_id: 717861
                            food_name: "Garlic Chicken"
                            amount: 1
                            units: "serving"
              plan_title: "My Weekly Meal Plan"
              include_groceries: true
              include_recipes: true
              format: "standard"
              start_date: "2026-03-03"
              output: "html"
      responses:
        '200':
          description: Rendered successfully
          content:
            text/html:
              schema:
                type: string
                description: Formatted HTML meal plan document
            application/pdf:
              schema:
                type: string
                format: binary
                description: PDF document of the meal plan
        '400':
          description: Bad request - invalid parameters or week data
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '500':
          description: Server error - failed to render
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'

  /gapi/usage/:
    get:
      operationId: getUsageSummary
      summary: Get API usage summary
      description: |
        Returns summarized usage and billing information for the authenticated API user
        across all of their API keys.

        By default, the summary starts at the most recent payment event with action
        `api`, `charge`, or `signup`. If no matching payment event exists, it starts
        at the first recorded API usage. If neither exists, no lower bound is applied.
        The default end is the current time.

        You can override the window with `start_date` and `end_date` in YYYY-MM-DD format.
        The response includes the resolved `start_date` and `end_date` used for the summary.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 0

        This endpoint accepts API key authentication via `X-API-Key`,
        `Authorization: Bearer <api_key>`, or a logged-in Django `sessionid` cookie.
        If API key credentials are provided, they are used; otherwise the user is
        resolved from the session.
      tags:
        - Usage
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
        - SessionCookieAuth: [ ]
      parameters:
        - name: start_date
          in: query
          required: false
          description: Inclusive start date for the usage window in YYYY-MM-DD format.
          schema:
            type: string
            format: date
            example: "2026-03-01"
        - name: end_date
          in: query
          required: false
          description: Inclusive end date for the usage window in YYYY-MM-DD format.
          schema:
            type: string
            format: date
            example: "2026-03-31"
      responses:
        '200':
          description: Usage summary retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/ApiUsageSummary'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - invalid date parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid API key or missing/invalid session
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'

  /gapi/keys/:
    get:
      operationId: getApiKeys
      summary: List API keys
      description: |
        Returns all API keys for the authenticated API user.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 0

        This endpoint accepts API key authentication via `X-API-Key`,
        `Authorization: Bearer <api_key>`, or a logged-in Django `sessionid` cookie.
        If API key credentials are provided, they are used; otherwise the user is
        resolved from the session.
      tags:
        - Keys
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
        - SessionCookieAuth: [ ]
      responses:
        '200':
          description: API keys retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKeySummary'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '401':
          description: Unauthorized - invalid API key or missing/invalid session
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
    post:
      operationId: createApiKey
      summary: Create API key
      description: |
        Creates a new API key for the authenticated user.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 0

        This endpoint accepts API key authentication via `X-API-Key`,
        `Authorization: Bearer <api_key>`, or a logged-in Django `sessionid` cookie.
        If API key credentials are provided, they are used; otherwise the user is
        resolved from the session.

        **Important:** The raw API key value (`api_key`) is only returned in this response.
        It cannot be retrieved again — store it securely immediately.
      tags:
        - Keys
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
        - SessionCookieAuth: [ ]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyRequest'
      responses:
        '200':
          description: API key created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/CreateApiKeyResponse'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - missing or invalid key_name
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid API key or missing/invalid session
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'

  /gapi/keys/{id}/:
    delete:
      operationId: deleteApiKey
      summary: Delete API key
      description: |
        Deletes an API key owned by the authenticated user.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 0

        This endpoint accepts API key authentication via `X-API-Key`,
        `Authorization: Bearer <api_key>`, or a logged-in Django `sessionid` cookie.
        If API key credentials are provided, they are used; otherwise the user is
        resolved from the session.
      tags:
        - Keys
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
        - SessionCookieAuth: [ ]
      parameters:
        - name: id
          in: path
          required: true
          description: ID of the API key to delete
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: API key deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        example: 'API key "My Key" deleted successfully'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - invalid key ID
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid API key or missing/invalid session
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '404':
          description: API key not found or does not belong to the authenticated user
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'

  /gapi/usage-history/:
    get:
      operationId: getUsageHistory
      summary: Get API usage history
      description: |
        Returns recent API request history for the authenticated user.

        If `start_date` is omitted, the window ends on tomorrow's date and includes the previous 31 days.
        If provided, `start_date` acts as the exclusive upper bound shown in the response and the history
        covers the 31 days before it.

        Results are ordered by newest request first. Use `page` to paginate through the selected
        history window. Each page returns up to 100 results.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 0

        This endpoint accepts API key authentication via `X-API-Key`,
        `Authorization: Bearer <api_key>`, or a logged-in Django `sessionid` cookie.
        If API key credentials are provided, they are used; otherwise the user is
        resolved from the session.
      tags:
        - Usage
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
        - SessionCookieAuth: [ ]
      parameters:
        - name: start_date
          in: query
          required: false
          description: Anchor date for the history window in YYYY-MM-DD format.
          schema:
            type: string
            format: date
            example: "2026-04-01"
        - name: page
          in: query
          required: false
          description: 1-based page number for paginated results.
          schema:
            type: integer
            format: int64
            minimum: 1
            default: 1
            example: 2
      responses:
        '200':
          description: Usage history retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/ApiUsageHistoryResponse'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - invalid start_date
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid API key or missing/invalid session
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'

  /gapi/groceries/:
    post:
      operationId: getGroceries
      summary: Build grocery list from week data
      description: |
        Takes the JSON output from `/gapi/generate/week/` and returns a structured grocery list as JSON.

        The endpoint reconstructs full food data from the database using the `food_id` values
        in the input, aggregates ingredients across all meals and days, and returns a consolidated
        grocery list sorted alphabetically by food name.

        Each grocery item includes the amount to buy, the unit of measurement, and a list of
        usages showing which meals and dates require that ingredient.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 10
      tags:
        - Groceries
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
      requestBody:
        required: true
        description: Grocery list request with week data
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GroceriesRequest'
            example:
              week_data:
                - day:
                    meals:
                      - meal_title: "Breakfast"
                        foods:
                          - food_id: 1234
                            food_name: "Scrambled Eggs"
                            amount: 2
                            units: "large"
                      - meal_title: "Lunch"
                        foods:
                          - food_id: 5678
                            food_name: "Grilled Chicken Salad"
                            amount: 1
                            units: "serving"
              start_date: "2026-03-03"
      responses:
        '200':
          description: Grocery list generated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    description: Array of grocery items sorted alphabetically by food name
                    items:
                      $ref: '#/components/schemas/GroceryFoodObject'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - invalid parameters or week data
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '500':
          description: Server error - failed to build grocery list
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'

  /gapi/food/{id}/:
    get:
      operationId: getFood
      summary: Get food by ID
      description: |
        Retrieves detailed information about a specific food item by its ID.

        <span style="line-height: 1; position: relative; top: -0.12em;">💳</span> <strong>Credits:</strong> 1
      tags:
        - Food
      security:
        - ApiKeyAuth: [ ]
        - BearerAuth: [ ]
      parameters:
        - name: id
          in: path
          required: true
          description: Food ID
          schema:
            type: integer
      responses:
        '200':
          description: Food details retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/Food'
                  warnings:
                    $ref: '#/components/schemas/IssueMap'
                  meta:
                    type: object
        '400':
          description: Bad request - invalid ID
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '401':
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'
        '404':
          description: Food not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  errors:
                    $ref: '#/components/schemas/IssueMap'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key authentication for private endpoints
    BearerAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'API key authentication using the literal header value: Bearer <api_key>'
    SessionCookieAuth:
      type: apiKey
      in: cookie
      name: sessionid
      description: Django session cookie authentication for API key and usage-management endpoints.

  schemas:
    WeekGenerateRequest:
      type: object
      example:
        target_calories: 2000
        num_meals: 3
        num_days: 7
        preset_diet: "anything"
      properties:
        target_calories:
          type: number
          description: Specifies the daily calorie target for the meal plan.
          example: 2000
        min_carbs:
          type: number
          description: |
            Optional. Specifies the minimum carbohydrates in grams for the daily meal plan.

            If not specified, defaults to 0.
        max_carbs:
          type: number
          description: |
            Optional. Specifies the maximum carbohydrates in grams for the daily meal plan.

            If not specified, defaults to 999999 (effectively unlimited).
        min_fats:
          type: number
          description: |
            Optional. Specifies the minimum fats in grams for the daily meal plan.

            If not specified, defaults to 0.
        max_fats:
          type: number
          description: |
            Optional. Specifies the maximum fats in grams for the daily meal plan.

            If not specified, defaults to 999999 (effectively unlimited).
        min_proteins:
          type: number
          description: |
            Optional. Specifies the minimum proteins in grams for the daily meal plan.

            If not specified, defaults to 0.
        max_proteins:
          type: number
          description: |
            Optional. Specifies the maximum proteins in grams for the daily meal plan.

            If not specified, defaults to 999999 (effectively unlimited).
        percent_carbs:
          type: number
          description: |
            Optional. Specifies the percentage of calories from carbohydrates (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros. If you provide percent macros, the gram-based min/max values will be ignored.
        percent_fats:
          type: number
          description: |
            Optional. Specifies the percentage of calories from fats (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros.
        percent_proteins:
          type: number
          description: |
            Optional. Specifies the percentage of calories from proteins (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros.
        num_meals:
          type: integer
          description: Specifies how many meals per day (typically 2-6).
          example: 3
        num_days:
          type: integer
          description: Number of days to generate (typically 7 for a week).
          example: 7
        preset_diet:
          type: string
          description: |
            Specifies the dietary preference/restriction.

            Valid values: `"anything"`, `"paleo"`, `"vegan"`, `"vegetarian"`, `"mediterranean"`, `"atkins / ketogenic"`, `"low-fodmap"`
          example: "anything"
        exclusions:
          type: array
          items:
            oneOf:
              - type: string
              - type: object
                additionalProperties: false
                properties:
                  label:
                    type: string
                  value:
                    type: string
                  food_ids:
                    type: array
                    items:
                      type: integer
                      format: int64
          description: |
            Optional. An array of keywords representing foods or ingredients to exclude from the meal plan.

            Each keyword will be converted to the special exclusion format that the meal generator expects.

            You can also pass objects with `food_ids` to exclude specific foods by ID.

            Example: `["dairy", "gluten", "peanuts"]`

            Common exclusion keywords: dairy, milk, cheese, yogurt, gluten, wheat, nuts, peanuts, tree nuts, soy, eggs, fish, shellfish, sesame, corn, etc.
          example:
            - "dairy"
            - "gluten"
        start_date:
          type: string
          description: Optional. Specifies the start date for the meal plan in YYYY-MM-DD format.
          example: "2025-01-15"
        include_groceries:
          type: boolean
          description: |
            Optional. Whether to include the aggregated grocery list in the JSON/HTML/PDF output.

            Defaults to `true`.
          default: true
          example: true
        recurring_food_ids_by_meal_name:
          type: object
          additionalProperties:
            type: array
            items:
              type: integer
              format: int64
          description: |
            Allows you to specify which recurring foods should be used for each meal.

            The key is the meal name (e.g., `"Breakfast"`, `"Lunch"`, `"Dinner"`, `"Snack 1"`), and the value is an array of recurring food IDs to consider.

            Example: `{"Breakfast": [123, 456], "Lunch": [789]}` means Breakfast should consider recurring foods 123 and 456, and Lunch should consider recurring food 789.

            Recurring foods have two frequency modes:
            - Frequency 0 (Often): The generator will prefer this food but it's not required
            - Frequency 1 (Always): The generator will always include this food in the meal

            If not specified or empty, the generator will use all recurring foods configured for the user's meal types.

    DayGenerateRequest:
      type: object
      example:
        target_calories: 2000
        num_meals: 3
        preset_diet: "anything"
      properties:
        target_calories:
          type: number
          description: Specifies the daily calorie target for the meal plan.
          example: 2000
        min_carbs:
          type: number
          description: |
            Optional. Specifies the minimum carbohydrates in grams for the daily meal plan.

            If not specified, defaults to 0.
        max_carbs:
          type: number
          description: |
            Optional. Specifies the maximum carbohydrates in grams for the daily meal plan.

            If not specified, defaults to 999999 (effectively unlimited).
        min_fats:
          type: number
          description: |
            Optional. Specifies the minimum fats in grams for the daily meal plan.

            If not specified, defaults to 0.
        max_fats:
          type: number
          description: |
            Optional. Specifies the maximum fats in grams for the daily meal plan.

            If not specified, defaults to 999999 (effectively unlimited).
        min_proteins:
          type: number
          description: |
            Optional. Specifies the minimum proteins in grams for the daily meal plan.

            If not specified, defaults to 0.
        max_proteins:
          type: number
          description: |
            Optional. Specifies the maximum proteins in grams for the daily meal plan.

            If not specified, defaults to 999999 (effectively unlimited).
        percent_carbs:
          type: number
          description: |
            Optional. Specifies the percentage of calories from carbohydrates (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros. If you provide percent macros, the gram-based min/max values will be ignored.
        percent_fats:
          type: number
          description: |
            Optional. Specifies the percentage of calories from fats (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros.
        percent_proteins:
          type: number
          description: |
            Optional. Specifies the percentage of calories from proteins (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros.
        num_meals:
          type: integer
          description: Specifies how many meals per day (typically 2-6).
          example: 3
        preset_diet:
          type: string
          description: |
            Specifies the dietary preference/restriction.

            Valid values: `"anything"`, `"paleo"`, `"vegan"`, `"vegetarian"`, `"mediterranean"`, `"atkins / ketogenic"`, `"low-fodmap"`
          example: "anything"
        exclusions:
          type: array
          items:
            oneOf:
              - type: string
              - type: object
                additionalProperties: false
                properties:
                  label:
                    type: string
                  value:
                    type: string
                  food_ids:
                    type: array
                    items:
                      type: integer
                      format: int64
          description: |
            Optional. An array of keywords representing foods or ingredients to exclude from the meal plan.

            Each keyword will be converted to the special exclusion format that the meal generator expects.

            You can also pass objects with `food_ids` to exclude specific foods by ID.

            Example: `["dairy", "gluten", "peanuts"]`

            Common exclusion keywords: dairy, milk, cheese, yogurt, gluten, wheat, nuts, peanuts, tree nuts, soy, eggs, fish, shellfish, sesame, corn, etc.
          example:
            - "dairy"
            - "gluten"
        recurring_food_ids_by_meal_name:
          type: object
          additionalProperties:
            type: array
            items:
              type: integer
              format: int64
          description: |
            Allows you to specify which recurring foods should be used for each meal.

            The key is the meal name (e.g., `"Breakfast"`, `"Lunch"`, `"Dinner"`, `"Snack 1"`), and the value is an array of recurring food IDs to consider.

            Example: `{"Breakfast": [123, 456], "Lunch": [789]}` means Breakfast should consider recurring foods 123 and 456, and Lunch should consider recurring food 789.

            Recurring foods have two frequency modes:
            - Frequency 0 (Often): The generator will prefer this food but it's not required
            - Frequency 1 (Always): The generator will always include this food in the meal

            If not specified or empty, the generator will use all recurring foods configured for the user's meal types.

    MealGenerateRequest:
      type: object
      example:
        target_calories: 500
        preset_diet: "anything"
        preferred_food_type: 3
      properties:
        target_calories:
          type: number
          description: Specifies the calorie target for the meal.
          example: 500
        min_carbs:
          type: number
          description: |
            Optional. Specifies the minimum carbohydrates in grams for the meal.

            If not specified, defaults to 0.
        max_carbs:
          type: number
          description: |
            Optional. Specifies the maximum carbohydrates in grams for the meal.

            If not specified, defaults to 999999 (effectively unlimited).
        min_fats:
          type: number
          description: |
            Optional. Specifies the minimum fats in grams for the meal.

            If not specified, defaults to 0.
        max_fats:
          type: number
          description: |
            Optional. Specifies the maximum fats in grams for the meal.

            If not specified, defaults to 999999 (effectively unlimited).
        min_proteins:
          type: number
          description: |
            Optional. Specifies the minimum proteins in grams for the meal.

            If not specified, defaults to 0.
        max_proteins:
          type: number
          description: |
            Optional. Specifies the maximum proteins in grams for the meal.

            If not specified, defaults to 999999 (effectively unlimited).
        percent_carbs:
          type: number
          description: |
            Optional. Specifies the percentage of calories from carbohydrates (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros. If you provide percent macros, the gram-based min/max values will be ignored.
        percent_fats:
          type: number
          description: |
            Optional. Specifies the percentage of calories from fats (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros.
        percent_proteins:
          type: number
          description: |
            Optional. Specifies the percentage of calories from proteins (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros.
        preset_diet:
          type: string
          description: |
            Specifies the dietary preference/restriction.

            Valid values: `"anything"`, `"paleo"`, `"vegan"`, `"vegetarian"`, `"mediterranean"`, `"atkins / ketogenic"`, `"low-fodmap"`
          example: "anything"
        exclusions:
          type: array
          items:
            oneOf:
              - type: string
              - type: object
                additionalProperties: false
                properties:
                  label:
                    type: string
                  value:
                    type: string
                  food_ids:
                    type: array
                    items:
                      type: integer
                      format: int64
          description: |
            Optional. An array of keywords representing foods or ingredients to exclude from the meal.

            Each keyword will be converted to the special exclusion format that the meal generator expects.

            You can also pass objects with `food_ids` to exclude specific foods by ID.

            Example: `["dairy", "gluten", "peanuts"]`

            Common exclusion keywords: dairy, milk, cheese, yogurt, gluten, wheat, nuts, peanuts, tree nuts, soy, eggs, fish, shellfish, sesame, corn, etc.
          example:
            - "dairy"
            - "gluten"
        recurring_food_ids:
          type: array
          items:
            type: integer
          description: |
            Allows you to specify which recurring foods should be considered for this meal.

            This is an array of recurring food IDs.

            Example: `[123, 456]` means the generator should consider recurring foods 123 and 456.

            Recurring foods have two frequency modes:
            - Frequency 0 (Often): The generator will prefer this food but it's not required
            - Frequency 1 (Always): The generator will always include this food

            If not specified or empty, the generator will use all recurring foods configured for the user.
        preferred_food_type:
          type: integer
          description: |
            Optional. Specifies which food type to prefer during generation.

            Valid values:
            - `0` - All food types (default)
            - `1` - Breakfast
            - `2` - Lunch
            - `3` - Dinner
            - `4` - Snack
            - `5` - Dessert

            If not specified, defaults to `0` (All food types).
          example: 3

    FoodObjectGenerateRequest:
      type: object
      example:
        target_calories: 500
        preset_diet: "anything"
        preferred_food_type: 3
      properties:
        target_calories:
          type: number
          description: Specifies the calorie target for the food object.
          example: 500
        min_carbs:
          type: number
          description: |
            Optional. Specifies the minimum carbohydrates in grams for the food object.

            If not specified, defaults to 0.
        max_carbs:
          type: number
          description: |
            Optional. Specifies the maximum carbohydrates in grams for the food object.

            If not specified, defaults to 999999 (effectively unlimited).
        min_fats:
          type: number
          description: |
            Optional. Specifies the minimum fats in grams for the food object.

            If not specified, defaults to 0.
        max_fats:
          type: number
          description: |
            Optional. Specifies the maximum fats in grams for the food object.

            If not specified, defaults to 999999 (effectively unlimited).
        min_proteins:
          type: number
          description: |
            Optional. Specifies the minimum proteins in grams for the food object.

            If not specified, defaults to 0.
        max_proteins:
          type: number
          description: |
            Optional. Specifies the maximum proteins in grams for the food object.

            If not specified, defaults to 999999 (effectively unlimited).
        percent_carbs:
          type: number
          description: |
            Optional. Specifies the percentage of calories from carbohydrates (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros. If you provide percent macros, the gram-based min/max values will be ignored.
        percent_fats:
          type: number
          description: |
            Optional. Specifies the percentage of calories from fats (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros.
        percent_proteins:
          type: number
          description: |
            Optional. Specifies the percentage of calories from proteins (0-100).

            **Important:** When using percent-based macros, all three fields (`percent_carbs`, `percent_fats`, `percent_proteins`) must be provided and must add up to 100.

            Percent-based macros take priority over gram-based min/max macros.
        preset_diet:
          type: string
          description: |
            Specifies the dietary preference/restriction.

            Valid values: `"anything"`, `"paleo"`, `"vegan"`, `"vegetarian"`, `"mediterranean"`, `"atkins / ketogenic"`, `"low-fodmap"`
          example: "anything"
        exclusions:
          type: array
          items:
            oneOf:
              - type: string
              - type: object
                additionalProperties: false
                properties:
                  label:
                    type: string
                  value:
                    type: string
                  food_ids:
                    type: array
                    items:
                      type: integer
                      format: int64
          description: |
            Optional. An array of keywords representing foods or ingredients to exclude from the food object.

            Each keyword will be converted to the special exclusion format that the meal generator expects.

            You can also pass objects with `food_ids` to exclude specific foods by ID.

            Example: `["dairy", "gluten", "peanuts"]`

            Common exclusion keywords: dairy, milk, cheese, yogurt, gluten, wheat, nuts, peanuts, tree nuts, soy, eggs, fish, shellfish, sesame, corn, etc.
          example:
            - "dairy"
            - "gluten"
        recurring_food_ids:
          type: array
          items:
            type: integer
          description: |
            Allows you to specify which recurring foods should be considered for this food object.

            This is an array of recurring food IDs.

            Example: `[123, 456]` means the generator should consider recurring foods 123 and 456.

            Recurring foods have two frequency modes:
            - Frequency 0 (Often): The generator will prefer this food but it's not required
            - Frequency 1 (Always): The generator will always include this food

            If not specified or empty, the generator will use all recurring foods configured for the user.
        preferred_food_type:
          type: integer
          description: |
            Optional. Specifies which food type to prefer during generation.

            Valid values:
            - `0` - All food types (default)
            - `1` - Breakfast
            - `2` - Lunch
            - `3` - Dinner
            - `4` - Snack
            - `5` - Dessert

            If not specified, defaults to `0` (All food types).
          example: 3

    WeekResponse:
      type: array
      description: Array of day entries for the week
      items:
        type: object
        properties:
          day:
            $ref: '#/components/schemas/DietPlan'
      example:
        - day:
            weekday: 1
            date: "2026-03-04"
            meals:
              - meal_title: "Breakfast"
                foods:
                  - food_id: 1234
                    food_name: "Scrambled Eggs"
                    amount: 2
                    units: "large"
                    food:
                      calories: 72
                      proteins: 6.3
                      fats: 4.8
                      carbs: 0.4
              - meal_title: "Lunch"
                foods:
                  - food_id: 5678
                    food_name: "Grilled Chicken Salad"
                    amount: 1
                    units: "serving"
                    food:
                      calories: 350
                      proteins: 42
                      fats: 12
                      carbs: 18

    DayResponse:
      $ref: '#/components/schemas/DietPlan'

    MealResponse:
      type: object
      description: |
        A generated meal containing food items. Unlike meals in week/day responses,
        the standalone meal response does not include `meal_title`.
      properties:
        foods:
          type: array
          description: Array of food items in this meal
          items:
            $ref: '#/components/schemas/FoodObject'
      example:
        foods:
          - food_id: 1234
            food_name: "Greek Yogurt"
            amount: 1
            units: "cup"
            food:
              calories: 130
              proteins: 11
              fats: 0
              carbs: 19
          - food_id: 5678
            food_name: "Mixed Berries"
            amount: 0.5
            units: "cup"
            food:
              calories: 35
              proteins: 0.5
              fats: 0.2
              carbs: 8.5

    FoodObjectResponse:
      $ref: '#/components/schemas/FoodObject'

    DietPlan:
      type: object
      description: A day's meal plan containing multiple meals
      properties:
        date:
          type: string
          description: Date for the day in YYYY-MM-DD format (when available).
          example: "2026-03-04"
        weekday:
          type: integer
          description: Day of the week (0 = Sunday, 1 = Monday, etc.).
          example: 1
        meals:
          type: array
          description: Array of meals for this day
          items:
            $ref: '#/components/schemas/Meal'
      example:
        meals:
          - meal_title: "Breakfast"
            foods:
              - food_id: 1234
                food_name: "Oatmeal"
                amount: 1
                units: "cup"
                food:
                  calories: 150
                  proteins: 5
                  fats: 3
                  carbs: 27
          - meal_title: "Lunch"
            foods:
              - food_id: 5678
                food_name: "Turkey Sandwich"
                amount: 1
                units: "sandwich"
                food:
                  calories: 320
                  proteins: 28
                  fats: 9
                  carbs: 35

    Meal:
      type: object
      description: A single meal with its food items
      properties:
        meal_title:
          type: string
          description: The name of the meal (e.g., "Breakfast", "Lunch", "Dinner")
          example: "Breakfast"
        foods:
          type: array
          description: Array of food items in this meal
          items:
            $ref: '#/components/schemas/FoodObject'
      example:
        meal_title: "Breakfast"
        foods:
          - food_id: 1234
            food_name: "Greek Yogurt"
            amount: 1
            units: "cup"
            food:
              calories: 130
              proteins: 11
              fats: 0
              carbs: 19
          - food_id: 5678
            food_name: "Mixed Berries"
            amount: 0.5
            units: "cup"
            food:
              calories: 35
              proteins: 0.5
              fats: 0.2
              carbs: 8.5

    FoodObject:
      type: object
      description: |
        A food item with serving size and nutritional information.

        Note: The `food` object contains nutritional macros per serving. The actual calories/macros
        consumed are calculated as: (food.calories * amount) based on the unit size.
      properties:
        food_id:
          type: integer
          description: Unique identifier for the food
          example: 789
        food_name:
          type: string
          description: Name of the food
          example: "Chicken Breast"
        amount:
          type: number
          description: Quantity of the food in the specified units
          example: 4
        units:
          type: string
          description: Unit of measurement for the amount (e.g., "oz", "g", "cup", "serving")
          example: "oz"
        food:
          $ref: '#/components/schemas/Food'
      example:
        food_id: 1234
        food_name: "Grilled Chicken Breast"
        amount: 6
        units: "oz"
        food:
          calories: 165
          proteins: 31
          fats: 3.6
          carbs: 0

    Food:
      type: object
      description: Core nutritional information for a food (macros per serving)
      properties:
        calories:
          type: number
          description: Calories per serving
          example: 165
        proteins:
          type: number
          description: Protein in grams per serving
          example: 31
        fats:
          type: number
          description: Fat in grams per serving
          example: 3.6
        carbs:
          type: number
          description: Carbohydrates in grams per serving
          example: 0
    IssueMap:
      type: object
      additionalProperties:
        type: string

    RenderRequest:
      type: object
      required:
        - week_data
      properties:
        week_data:
          type: array
          description: |
            The week data to render, as an array of day entries. This is the same array
            returned in the `results` field of the `/gapi/generate/week/` response.

            **Important:** The `food_id` values are used to fetch full food data from the database,
            enabling complete recipe and nutritional rendering.
          items:
            type: object
            properties:
              day:
                $ref: '#/components/schemas/DietPlan'
        output:
          type: string
          description: |
            Optional. Output format for rendering.

            Valid values: `html` (default), `pdf`.
          enum:
            - html
            - pdf
          default: html
        plan_title:
          type: string
          description: |
            Optional. Custom title for the meal plan document.

            If not specified, defaults to "Meal Plan".
          example: "My Weekly Meal Plan"
        include_groceries:
          type: boolean
          description: |
            Optional. Whether to include the aggregated grocery list in the output.

            Defaults to `true`.
          default: true
          example: true
        include_recipes:
          type: boolean
          description: |
            Optional. Whether to include full recipe instructions in the output.

            Defaults to `true`.
          default: true
          example: true
        format:
          type: string
          description: |
            Optional. The print/template format to use for rendering.

            Valid values:
            - `standard` - Default format with balanced detail
            - `extra_nutrition` - Includes additional nutritional details
            - `condensed` - Compact format with less detail
            - `plaintext` - Simple text format

            If not specified, defaults to `standard`.
          enum:
            - standard
            - extra_nutrition
            - condensed
            - plaintext
          default: standard
          example: "standard"
        start_date:
          type: string
          description: |
            Optional. Start date for the meal plan in YYYY-MM-DD format.

            This date will be used as Day 1 of the meal plan, with subsequent days
            incrementing from this date.

            If not specified, defaults to today's date.
          example: "2026-03-03"
      example:
        week_data:
          - day:
              weekday: 1
              meals:
                - meal_title: "Breakfast"
                  foods:
                    - food_id: 1234
                      food_name: "Scrambled Eggs"
                      amount: 2
                      units: "large"
                - meal_title: "Lunch"
                  foods:
                    - food_id: 5678
                      food_name: "Grilled Chicken Salad"
                      amount: 1
                      units: "serving"
        plan_title: "My Weekly Meal Plan"
        include_groceries: true
        include_recipes: true
        format: "standard"
        start_date: "2026-03-03"
        output: "html"

    GroceriesRequest:
      type: object
      required:
        - week_data
      properties:
        week_data:
          type: array
          description: |
            The week data to build a grocery list from, as an array of day entries. This is the same
            array returned in the `results` field of the `/gapi/generate/week/` response.

            **Important:** The `food_id` values are used to fetch full food data from the database,
            enabling accurate ingredient aggregation and unit conversion.
          items:
            type: object
            properties:
              day:
                $ref: '#/components/schemas/DietPlan'
        start_date:
          type: string
          description: |
            Optional. Start date for the meal plan in YYYY-MM-DD format.

            Used to set dates on grocery item usages.

            If not specified, defaults to today's date.
          example: "2026-03-03"
      example:
        week_data:
          - day:
              meals:
                - meal_title: "Breakfast"
                  foods:
                    - food_id: 1234
                      food_name: "Scrambled Eggs"
                      amount: 2
                      units: "large"
                - meal_title: "Lunch"
                  foods:
                    - food_id: 5678
                      food_name: "Grilled Chicken Salad"
                      amount: 1
                      units: "serving"
        start_date: "2026-03-03"

    GroceryFoodObject:
      type: object
      description: A grocery item with aggregated amounts and usage details
      properties:
        food_id:
          type: integer
          description: Unique identifier for the food
          example: 2021
        food_name:
          type: string
          description: Name of the food
          example: "White mushrooms"
        food_group_id:
          type: integer
          nullable: true
          description: Food group identifier for categorization
          example: 1100
        units:
          type: integer
          nullable: true
          description: Unit index into the food's weights array
        scaled_amount_to_buy:
          type: number
          description: The calculated amount to purchase in the specified units
          example: 3.5
        scaled_amount_needed_for_dates:
          type: number
          description: Total scaled amount needed across all usages
        scaled_amount_owned:
          type: number
          description: Amount already owned (always 0 for this endpoint)
        is_normalized:
          type: boolean
          description: Whether the food amounts have been normalized to a common unit
        user_modified:
          type: boolean
          description: Whether the user has modified this grocery item (always false for this endpoint)
        usages:
          type: array
          description: List of meal plan usages that require this grocery item
          items:
            type: object
            properties:
              food_name:
                type: string
                description: Name of the recipe or food that uses this ingredient
              meal_name:
                type: string
                description: Name of the meal (e.g., "Breakfast", "Lunch")
              food_id:
                type: integer
                description: ID of the recipe or food that uses this ingredient
              date:
                type: string
                description: Date of the meal in YYYY-MM-DD format
              ingredient_scaled_amount:
                type: number
                description: Amount of this ingredient needed for the usage
              ingredient_units:
                type: integer
                description: Unit index for this ingredient usage
              ingredient_food_id:
                type: integer
                description: Food ID of the ingredient
        resource_uri:
          type: string
          description: API resource URI for this grocery item

    ApiUsageSummary:
      type: object
      description: Aggregated usage and billing totals for the authenticated API user across all API keys
      properties:
        start_date:
          type: string
          format: date
          nullable: true
          description: Resolved inclusive start date for the usage window. Null when no lower bound was applied.
          example: "2026-04-01"
        end_date:
          type: string
          format: date
          description: Resolved inclusive end date for the usage window.
          example: "2026-04-30"
        credits_used:
          type: integer
          format: int64
          example: 12000
        base_credits:
          type: integer
          format: int64
          example: 1000
        billable_credits:
          type: integer
          format: int64
          example: 11000
        price_per_credit:
          type: number
          format: double
          example: 0.01
        billable_charges:
          type: number
          format: double
          example: 110.0
        currency:
          type: string
          example: "USD"
        per_key_totals:
          type: array
          items:
            $ref: '#/components/schemas/ApiKeyUsageTotal'

    ApiKeyUsageTotal:
      type: object
      description: Usage totals for a single API key within the selected window
      properties:
        api_key_id:
          type: integer
          format: int64
          nullable: true
          example: 42
        credits_used:
          type: integer
          format: int64
          example: 5000

    ApiUsageHistoryResponse:
      type: object
      description: Windowed API request history for the authenticated user
      properties:
        requested_start_date:
          type: string
          format: date
          example: "2026-04-01"
        start_date:
          type: string
          format: date
          example: "2026-03-01"
        end_date:
          type: string
          format: date
          example: "2026-04-01"
        page:
          type: integer
          format: int64
          example: 2
        page_size:
          type: integer
          format: int64
          example: 100
        total_results:
          type: integer
          format: int64
          example: 142
        total_pages:
          type: integer
          format: int64
          example: 2
        has_next_page:
          type: boolean
          example: true
        has_previous_page:
          type: boolean
          example: true
        results:
          type: array
          items:
            $ref: '#/components/schemas/ApiUsageHistoryItem'

    ApiUsageHistoryItem:
      type: object
      description: A single recorded API request
      properties:
        id:
          type: integer
          format: int64
          example: 12345
        api_key_id:
          type: integer
          format: int64
          nullable: true
          example: 42
        endpoint:
          type: string
          example: "/gapi/generate/week/"
        status_code:
          type: integer
          example: 200
        error_message:
          type: string
          example: ""
        date:
          type: string
          format: date-time
          example: "2026-03-31T12:34:56.789Z"
        credits_used:
          type: integer
          format: int64
          example: 5000

    ApiKeySummary:
      type: object
      description: A single API key owned by the authenticated API user
      properties:
        id:
          type: integer
          format: int64
          example: 42
        key_name:
          type: string
          example: "Production Key"
        key_prefix:
          type: string
          example: "etm_live"
        is_active:
          type: boolean
          example: true
        created_at:
          type: string
          format: date-time
          example: "2026-04-01T10:30:00Z"
        last_used_at:
          type: string
          format: date-time
          nullable: true
          example: "2026-04-08T09:15:00Z"

    CreateApiKeyRequest:
      type: object
      required:
        - key_name
      properties:
        key_name:
          type: string
          description: A human-readable name for this API key.
          example: "Production Key"

    CreateApiKeyResponse:
      type: object
      description: |
        The newly created API key. The `api_key` field contains the raw key value and is
        **only returned once** — it cannot be retrieved again after this response.
      properties:
        api_key:
          type: string
          description: The full raw API key. Store this securely — it will not be shown again.
          example: "aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789ABCD"
        id:
          type: integer
          format: int64
          example: 42
        key_name:
          type: string
          example: "Production Key"
        key_prefix:
          type: string
          description: The first 8 characters of the key, used for identification.
          example: "aBcDeFgH"
        is_active:
          type: boolean
          example: true
        expires_at:
          type: string
          format: date-time
          nullable: true
          example: null
        created_at:
          type: string
          format: date-time
          example: "2026-04-01T10:30:00Z"
