{
  "openapi": "3.1.0",
  "info": {
    "title": "TrackCaffeine public data API",
    "version": "1.0.0",
    "summary": "Caffeine content, the elimination model, the guide index, the calculators and the app.",
    "description": "Static JSON, keyless, CORS-open, licensed CC BY 4.0 with attribution. Every figure is either a public-source estimate compiled by hand for a labelled serving, or computed by the same JavaScript module the site's calculators run — see /api/v1/model.json.\n\nNothing here is medical advice, and no endpoint recommends an amount of caffeine to take.",
    "contact": {
      "name": "TrackCaffeine",
      "url": "https://trackcaffeine.com/contacts/",
      "email": "support@trackcaffeine.com"
    },
    "license": {
      "name": "CC BY 4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    }
  },
  "servers": [
    {
      "url": "https://trackcaffeine.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Human documentation",
    "url": "https://trackcaffeine.com/api/"
  },
  "paths": {
    "/api/v1/index.json": {
      "get": {
        "summary": "What this API contains",
        "description": "One fetch that lists every endpoint, the licence and the agent surface.",
        "responses": {
          "200": {
            "description": "The dataset, with its licence, provenance and caveats in the envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Index"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/drinks.json": {
      "get": {
        "summary": "Caffeine content of 87 drinks",
        "description": "The 87 drinks the site and the app share, each with the milligrams for a labelled serving and what the model computes from it: decay at 1/3/6/9/12 hours, time to half, hours until it is back under the 100 mg sleep threshold, and cups-of-drip-coffee equivalent.\n\nREAD `scales_with_volume` BEFORE SCALING. Where it is false, a larger size does not carry proportionally more caffeine — a bigger latte is more milk, not more coffee — and multiplying by a size ratio is the most common way this data is misused.\n\nREAD `crosses_sleep_threshold`, NOT THE ZERO. `hours_to_sleep_threshold` is 0 for a drink that never reaches 100 mg, which means \"never crosses it\", not \"crosses it immediately\".",
        "responses": {
          "200": {
            "description": "The dataset, with its licence, provenance and caveats in the envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DrinkSet"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/model.json": {
      "get": {
        "summary": "The caffeine elimination model",
        "description": "Constants, formulas, worked examples computed by the engine itself, and the limits of the model. First-order elimination with no absorption phase, so the first hour after a dose is overstated; the half-life is a population average and real values span 1.5 to 10+ hours.",
        "responses": {
          "200": {
            "description": "The dataset, with its licence, provenance and caveats in the envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Model"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/guides.json": {
      "get": {
        "summary": "The guide index",
        "description": "Title, description, dates, URL, markdown twin and per-guide locale availability. Titles and metadata only — `locales` is computed from what was built, so a guide is never advertised in a language it does not exist in.",
        "responses": {
          "200": {
            "description": "The dataset, with its licence, provenance and caveats in the envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuideSet"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools.json": {
      "get": {
        "summary": "The free browser calculators",
        "description": "Four calculators, their inputs and outputs, the engine functions behind each, and the MCP tool that computes the same figure. Everything runs in the reader's browser: no account, nothing uploaded.",
        "responses": {
          "200": {
            "description": "The dataset, with its licence, provenance and caveats in the envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSet"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/product.json": {
      "get": {
        "summary": "What the CoffeeLog app does",
        "description": "Capabilities, each with the page that documents it; the platforms; the pricing shape; a `not_included` block naming what the app will not do; and every processor that receives anything from the device.",
        "responses": {
          "200": {
            "description": "The dataset, with its licence, provenance and caveats in the envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/status.json": {
      "get": {
        "summary": "Dataset freshness",
        "description": "Build date and row count per dataset, plus what makes each one change. Use it to decide whether to refetch.",
        "responses": {
          "200": {
            "description": "The dataset, with its licence, provenance and caveats in the envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Licence": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "const": "CC BY 4.0"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "attribution": {
            "type": "string"
          },
          "terms": {
            "type": "string"
          }
        }
      },
      "Envelope": {
        "type": "object",
        "description": "Shared by every dataset: what it is, when it was built, and how it may be used.",
        "properties": {
          "dataset": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "generated": {
            "type": "string",
            "format": "date",
            "description": "Build date. A date, not a timestamp: a second-precision stamp would make every endpoint look revised on every deploy."
          },
          "count": {
            "type": "integer"
          },
          "self": {
            "type": "string",
            "format": "uri"
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "licence": {
            "$ref": "#/components/schemas/Licence"
          },
          "provenance": {
            "type": "object",
            "description": "Where each column came from, and the statement that none of it is redistributed from a licensed corpus — which is why the whole set can carry CC BY."
          }
        }
      },
      "Drink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable identifier, shared with the app."
          },
          "slug": {
            "type": "string",
            "description": "URL slug, derived from brand + name."
          },
          "name": {
            "type": "string"
          },
          "brand": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "espresso",
              "brewed",
              "coffeeShop",
              "energy",
              "tea",
              "soda",
              "preworkout",
              "other"
            ]
          },
          "serving_ml": {
            "type": "number",
            "description": "The serving the milligram figure is for."
          },
          "caffeine_mg": {
            "type": "number",
            "description": "For `serving_ml`, not per 100 ml and not per container where those differ. A public-source estimate for a labelled serving, never a measurement."
          },
          "caffeine_per_100ml": {
            "type": [
              "number",
              "null"
            ],
            "description": "null where a per-volume figure is meaningless — an espresso shot, a pill or a scoop is a unit, not a volume."
          },
          "is_decaf": {
            "type": "boolean",
            "description": "Decaffeinated, not caffeine-free: these rows carry a real small figure."
          },
          "scales_with_volume": {
            "type": "boolean",
            "description": "false means a larger size does NOT carry proportionally more caffeine. Do not multiply by a size ratio on such a row."
          },
          "cups_of_drip_coffee": {
            "type": "number",
            "description": "In 240 ml cups at 96 mg — the unit a reader pictures. Distinct from the calculator's 95 mg standard-cup unit."
          },
          "remaining_mg": {
            "type": "object",
            "description": "Computed: mg left after 1, 3, 6, 9 and 12 hours at the default half-life."
          },
          "hours_to_half": {
            "type": "number"
          },
          "hours_to_sleep_threshold": {
            "type": "number",
            "description": "Hours for this serving ALONE to decay to 100 mg. 0 means it never reaches the threshold — check `crosses_sleep_threshold`. Not time until the caffeine is gone, and it ignores anything else drunk that day."
          },
          "crosses_sleep_threshold": {
            "type": "boolean"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "DrinkSet": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Envelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Drink"
                }
              }
            }
          }
        ]
      },
      "Model": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Envelope"
          },
          {
            "type": "object",
            "properties": {
              "constants": {
                "type": "object"
              },
              "formulas": {
                "type": "object"
              },
              "worked_examples": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "limits": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "refusals": {
                "type": "object",
                "description": "What this site declines to do, read from the engine module rather than re-worded."
              }
            }
          }
        ]
      },
      "Guide": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string",
            "description": "The page H1."
          },
          "seo_title": {
            "type": "string",
            "description": "Deliberately different from the H1 — different queries, not cosmetics."
          },
          "description": {
            "type": "string"
          },
          "page_type": {
            "type": "string"
          },
          "published": {
            "type": "string",
            "format": "date"
          },
          "modified": {
            "type": "string",
            "format": "date",
            "description": "A real revision date, never bumped to fake freshness."
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "markdown": {
            "type": "string",
            "format": "uri",
            "description": "The markdown twin, about a tenth the bytes."
          },
          "locales": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Computed from what was actually built."
          }
        }
      },
      "GuideSet": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Envelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Guide"
                }
              }
            }
          }
        ]
      },
      "ToolSet": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Envelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          }
        ]
      },
      "Product": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Envelope"
          },
          {
            "type": "object",
            "properties": {
              "product": {
                "type": "object"
              },
              "platforms": {
                "type": "object"
              },
              "pricing": {
                "type": "object",
                "description": "`subscription_price` is null on purpose: it is priced in App Store Connect, and a price typed into a repository goes stale silently."
              },
              "capabilities": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "not_included": {
                "type": "object",
                "description": "Explicit false values. An absent field reads as an oversight; a false reads as an answer."
              },
              "data_leaves_device": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          }
        ]
      },
      "Index": {
        "type": "object"
      },
      "Status": {
        "type": "object"
      }
    }
  },
  "x-generated": "2026-09-30"
}
