Your data, in your own tools

No generated prose in the app. Point your own LLM at your record instead, and push anything you can measure into it.

Start with a key

Both surfaces authenticate with an API key you mint yourself, in Settings. A key starts with ql_, is shown once and never again, and is stored here only as a hash — there is nothing in our database to steal and nothing to rotate out of a backup. Revoking one deletes it; the next request with it gets a 401.

A key carries one or both of two scopes, and nothing else. A Read key reaches your metrics, series, correlations, standings, goals and coverage. A Push key writes points and does nothing else. Neither one can touch billing, your profile, the export archive, account deletion, or key management itself — those stay behind a signed-in session, so a leaked key cannot mint its siblings or take your whole record in one request. It can read the data this app charts, which is real damage; revoke it and it stops.

Keys are part of the paid plan. Minting one needs an active plan, and keys you already hold keep reading if your plan lapses — a paywall between you and your own history, through your own tools, would be the same lie in a script that it would be in the app. Pushing is a write, so it stops with everything else.

Your LLM reads your metrics

The endpoint below is an MCP server over your own record. Point any MCP client at it and your assistant can ask real questions — how deep sleep tracked squat volume this quarter, which correlations survived correction, what your coverage actually looks like before it draws a conclusion from a gap.

Every tool is the same read the app itself makes, called in the same process and answered with the same bytes. That is the point rather than an implementation note: nothing is summarised for the model, no second pipeline can drift from the first, and any number your assistant quotes is a number you can find on a chart here.

https://my.quantifiedlife.io/rpc/mcp

Streamable HTTP, stateless. The key goes in the Authorization header as a bearer token, never in the URL — keys in query strings end up in server logs and shell histories.

Configure your client

Claude Code

Run this once; the key is stored in your Claude Code config.

claude mcp add --transport http quantifiedlife https://my.quantifiedlife.io/rpc/mcp \
  --header "Authorization: Bearer ql_YOUR_KEY_HERE"

Claude Desktop

Add this to claude_desktop_config.json, then restart the app.

{
  "mcpServers": {
    "quantifiedlife": {
      "type": "http",
      "url": "https://my.quantifiedlife.io/rpc/mcp",
      "headers": {
        "Authorization": "Bearer ql_YOUR_KEY_HERE"
      }
    }
  }
}

Any other client

For clients that cannot set a header themselves. Note there is no space after the colon — mcp-remote splits its arguments on spaces.

{
  "mcpServers": {
    "quantifiedlife": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://my.quantifiedlife.io/rpc/mcp",
        "--header",
        "Authorization:Bearer ql_YOUR_KEY_HERE"
      ]
    }
  }
}

Replace ql_YOUR_KEY_HERE with the key you minted. Settings shows the same three snippets with your own app URL already filled in.

The 12 tools, and nothing else

The roster is curated, not generated from whatever the server happens to expose. Nothing in it writes.

ToolWhat it answers
list_metricsevery metric your sources publish, with its unit, kind and tags
get_metric_seriesa bucketed series — any window, aggregation, tag filter or split
get_metric_pointsthe raw points, at their real timestamps
list_tag_valueswhat a tag key actually contains, for filtering
get_correlationsthe findings, with their corrections, sample sizes and exclusions
list_standingswhere you rank on the rulers you have data for
get_standing_sheetone ruler in full, with its population and its method
list_goalsyour goals and where each one stands
get_goal_seriesa goal's progress over time
get_coveragewhich days each source actually has, and which are gaps
get_sectionsthe main page at rest — five category sections, each with its served sentence, hero picture and row
get_sectionone section opened — every checked reading, its pictures, direction, refusals and coverage

Journal metrics — moods, symptoms, habits — read like any other metric. Your journal prose does not: free text is not on the roster.

Anything a script can measure becomes a source

A Push key turns one curlinto a data source. A cron job, a smart-scale script, a webhook, a number you type in by hand once a week — if you can measure it, it lands in the same datastore as everything else and charts, correlates, and enters goals unmodified. This is what the incumbents close as “not possible”: nobody needs a partner deal to write to your own record.

One call registers a metric and writes it

Custom names live under /custom, a root reserved for you, so no source we ship later can collide with a name you already own. The first push declares the metric — what kind it is, what unit, how it aggregates — and writes its points in the same request.

curl https://my.quantifiedlife.io/rpc/quantifiedlife.v1.PushService/PutPoints \
  -H "Authorization: Bearer ql_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
  "window": {
    "start": { "year": 2026, "month": 8, "day": 26 },
    "end": { "year": 2026, "month": 8, "day": 27 }
  },
  "metrics": ["/custom/coffee/cups"],
  "declare": [
    {
      "name": "/custom/coffee/cups",
      "kind": "METRIC_KIND_PERIOD_TOTAL",
      "value_type": "VALUE_TYPE_INT64",
      "default_aggregation": "AGGREGATION_SUM",
      "unit": "1",
      "anchor_class": "ANCHOR_CLASS_LOCAL_DAY",
      "description": "Cups of coffee"
    }
  ],
  "points": [
    {
      "metric": "/custom/coffee/cups",
      "time": "2026-08-26T12:00:00Z",
      "int64_value": 3
    }
  ]
}'

That window is the honest part. It says these are the days I measured, so re-sending replaces rather than doubles — an at-least-once script needs no dedup state — and naming a metric with no points in it is a real statement: zero, measured, not missing. It is the same ledger every built-in source writes through, so a pushed metric gets the same coverage calendar, the same run history, and the same right to be audited.

Day-anchored metrics carry 12:00:00Z on the date they belong to; only metrics that mark a real instant use their real time.

Or append when it happens

Leave the window out and the batch asserts only its own points — the “curl when it happens” case. Give it an idempotency key and a retry replaces itself instead of writing twice. No declare block here, because the call above already registered the metric.

curl https://my.quantifiedlife.io/rpc/quantifiedlife.v1.PushService/PutPoints \
  -H "Authorization: Bearer ql_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
  "idempotency_key": "coffee-2026-08-27",
  "points": [
    {
      "metric": "/custom/coffee/cups",
      "time": "2026-08-27T12:00:00Z",
      "int64_value": 1
    }
  ]
}'

You can also feed a metric one of our sources already declares — /health/weightfrom your own scale script, say. The descriptor stays ours, your points keep their own provenance, neither feed deletes the other’s data, and charts can split by source.

The bounds

Server-enforced, and these are current values rather than promises carved in stone.

  • 5,000 points and 1 MiB per request.
  • A replace window covers at most 31 days — a bound on how much stored history one call can delete, not on how much you can send.
  • Timestamps may reach as far into the past as you like; backfill is the point. They may not run past tomorrow.
  • Up to 200 custom metrics per account. Delete one to declare another.
  • 120 reads and 60 pushes per minute per key, with room to burst. Past the top you get a 429 and a Retry-After that tells you exactly how long to wait.

Got a unit wrong? Delete the custom metric and its points go with it, and you can declare it again properly. A descriptor is otherwise immutable while points are stored under it — a metric that quietly changed meaning underneath a year of history is worse than one you have to re-declare.

Or skip MCP entirely

A Read key works on the app’s own read endpoints too — the same handlers, plain JSON over POST, under https://my.quantifiedlife.io/rpc/quantifiedlife.v1.<Service>/<Method>. It caches better than the app does, in fact: a browser session re-authenticates every hour and flushes its cache with it, while a key never rotates, so a polling script settles into permanent 304s.

And none of this is the only way out. Everything here is also in the one-click export — every point, tag and timestamp, as CSVs or one machine-readable file — whether or not you ever mint a key.

Why there is no chat box here

Every number this product shows is computed the same way twice and can be traced to the points it came from. That is a property we hold on to, not a stance we advertise: a summary generated over your health data is exactly the thing that congratulates you on an eleven-minute walk while ignoring the sixteen-mile run the same day.

The demand underneath “let me ask my data questions” is real, and it is a demand for agency. So it gets an endpoint instead of a chat box: your model, your prompt, your account, our numbers — and the model is a guest with a read-only key rather than a narrator we hired on your behalf. See the methods for how the numbers themselves are computed.

Built for the long run

Start the record today.

Honest correlations need months of overlapping history, so the best time to start collecting was months ago. The second best is now.

Start your free trial

30 days free · No credit card · Cancel any time · free sources continue · full plan $7 a month