addrbookFor publishers
← The evidence ledger

Evidence row 1500706

Observed , by probe.mcp/1.

Kind
mcp.tools
About
endpoint ef48ad6b-704a-4908-b2d4-2394394f5c32
Recorded
Previous row
#1478986
Body SHA-256
a5b7cb2712290b4046b1e942492cbdfe29b04280f4f5a15f8be3c3a3a786f95e
Leaf
0x1a33c4ef82199a80091f8756c0e83da4f82743d197f9fa5f4d5177be357c2930

What was observed

{
  "count": 30,
  "tools": [
    {
      "description": "Attach short dated notes to a series (e.g. 'Policy rate raised to 4.5%'); shown as markers on charts. Single-line plain text, max 280 chars; one note per date (re-adding replaces it); each counts as one data point.",
      "inputSchema": {
        "properties": {
          "annotations": {
            "description": "[{\"date\":\"2026-09-25\",\"text\":\"Policy rate raised to 4.5%\"}]",
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "series": {
            "description": "series name",
            "type": "string"
          }
        },
        "required": [
          "series",
          "annotations"
        ],
        "type": "object"
      },
      "name": "add_annotations"
    },
    {
      "description": "Create a hosted dashboard; returns its public URL. Dashboard spec: {\"title\": str, \"description\"?: str, \"range\"?: \"1m\"|\"3m\"|\"6m\"|\"ytd\"|\"1y\"|\"2y\"|\"5y\"|\"10y\"|\"max\", \"theme\"?: \"auto\"|\"light\"|\"dark\" (default mode; auto follows the viewer's system and viewers can switch), \"layout\"?: \"auto\"|\"overview\"|\"detail\" (only the default for panels without \"display\": overview makes them tiles, detail makes them charts, auto makes them tiles above 8 panels), \"visibility\"?: \"public\"|\"private\" (default public: anyone with the link; private: only members of this account signed in to tlly.dev, so embeds and shared links stop working for others), \"panels\": [{\"title\"?: str,\n\"series\": \"name\" | [\"a\",\"b\"], \"type\"?: \"line\"|\"area\"|\"bar\"|\"stat\"|\"gauge\"|\"bargauge\"|\"pie\"|\"heatmap\"|\"table\", \"unit\"?: str, \"note\"?: str, \"stale_after_days\"?: num,\n\"thresholds\"?: {\"red_above\"?, \"yellow_above\"?, \"yellow_below\"?, \"red_below\"?}, \"range\"?: (as above, overrides the dashboard's),\n\"section\"?: str (starts a new titled section at this panel), \"views\"?: bool (show the Level / Y/Y % / Index buttons; default: shown unless the unit is already a rate like %, y/y or pp), \"display\"?: \"tile\"|\"chart\" (tile: a compact number with a sparkline, click for the full chart; chart: a card with the full chart; mix them freely, e.g. tiles for the many supporting numbers and charts for the few that need their history), \"headline\"?: bool (show as a big number at the top of the dashboard, under a status strip; use for the 3-6 most important), \"width\"?: \"half\"|\"full\", \"scale\"?: \"linear\"|\"log\",\n\"id\"?: str (stable panel id, e.g. pn_abc123XYZ012; keep existing ids when editing so alerts stay attached),\n\"right_axis\"?: [series names] (line, area and bar charts: draw these series against a second y axis on the right, for series of different sizes or units; at least one stays on the left), \"right_unit\"?: str (unit of the right axis; \"unit\" is the left's), \"left_label\"?/\"right_label\"?: str (≤40 chars, a title written vertically along the left / right y axis, e.g. \"Policy rate\"; right_label needs right_axis),\n\"view\"?: \"level\"|\"yoy\"|\"index\" (initial view; viewers can switch), \"source\"?: str (shown as \"Source: …\"; defaults to the job's host),\n\"labels\"?: [display name per series], \"status_series\"?: name (a red/yellow/green text series that sets the panel colour),\n\"compare\"?: \"prev\"|\"3m\"|\"1y\" (what the change is measured against), \"period\"?: \"D\"|\"M\"|\"Q\" (show dates as 2026-06 or 2026Q2), \"value_names\"?: {\"green\": \"Hold\", …} (display names for text values)}],\n\"badge\"?: \"worst\" (default: the header badge shows the worst panel) | \"off\" | panel index (0-based; the badge follows that panel),\n\"shade\"?: [{\"from\": date, \"to\": date, \"label\"?: str}] (up to 20 shaded periods on every chart)}.\n\"type\": \"table\" shows one row per series (up to 20): label, latest value, date and a status dot; its \"status_series\" is a list with one entry per row (\"\" for none).\n\"area\" is a line chart filled underneath; \"gauge\" is a dial of the latest value with the threshold zones, and \"bargauge\" a horizontal bar per series (up to 20, coloured by thresholds), both scaled by \"min\"?/\"max\"?: num (default 0 to a bit above the largest value or threshold); \"pie\" is a donut of the series' latest values (shares of a whole, up to 20); \"heatmap\" is a calendar of the first series' daily values (good for activity per day).\nStatus colour is computed from the latest value of the panel's first series; a series of red/yellow/green labels (e.g. a derived status) colours itself.\nNotes may use {latest}, {date}, {period} and {change}, filled in from the panel's latest value. Annotations on a panel's series appear as markers.\nViewers can pick a time range (1M to Max, year to date, or a custom from-to period), zoom, switch views and download CSV; ?embed=1 hides the page chrome and &panel=N shows one panel.\nText limits: title ≤120, description ≤500, panel title ≤120, unit ≤20, note ≤280, section and source ≤80 characters, single line, plain text.",
      "inputSchema": {
        "properties": {
          "spec": {
            "description": "dashboard spec",
            "type": "object"
          }
        },
        "required": [
          "spec"
        ],
        "type": "object"
      },
      "name": "create_dashboard"
    },
    {
      "description": "Schedule a fetch job. It runs once immediately and the result is returned. Job spec: {\"series\": name, \"cron\": \"m h dom mon dow\" (UTC, at most every 5 min),\n\"request\": {\"url\", \"method\"?: \"GET\"|\"POST\", \"headers\"?: {}, \"body\"?: obj|str,\n            \"pages\"?: {\"param\": \"page\", \"start\"?: 0, \"max\"?: 1-10} (json with items: bump a query parameter until an empty items array)\n                     | {\"cursor\": \"paging.next.after\", \"into\": \"after\", \"max\"?: 1-10} (copy that JSON path from each response into the JSON body; stop when it is missing. A cursor that remains after max pages is an error, so a sum is never a partial total. HubSpot CRM search uses this. A json aggregate with no request.pages fails when the body still has another page, paging.next.after, has_more true, links.next, pageInfo.hasNextPage or another next cursor, instead of saving a partial total. Set pages. A response with no next marker is complete.)},\n\"extract\": {\"format\": \"json\", \"items\"?: \"path.to.array\", \"date\"?: \"field\", \"value\": \"field\", \"value_type\"?: \"number\"|\"text\",\n            \"aggregate\"?: \"count\"|\"sum\"|\"mean\"|\"min\"|\"max\" (records per date, e.g. hourly prices to a daily mean; needs items; all but count need value), \"period\"?: \"D\"|\"M\"|\"Q\",\n            \"where\"?: [{\"field\": \"path\", \"equals\"|\"not_equals\": value}] (up to 5, with aggregate). Omit \"date\" with aggregate to snapshot every matching record as one point on the day of the fetch. String numbers (HubSpot properties.amount) are summed.}\n         | {\"format\": \"json_columns\", \"dates\": \"path.to.array\", \"values\": \"path.to.array\", \"value_type\"?} (two parallel arrays, e.g. Yahoo chart: timestamp + close)\n         | {\"format\": \"csv\", \"date\"?: \"COLUMN\", \"value\": \"COLUMN\", \"delimiter\"?, \"value_type\"?} (omit date to stamp every row with the fetch day; one value per day, so the last row wins. That does not sum the rows the way a JSON aggregate that omits date does. A history needs a Date column. Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/copy. Public CSV https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/export?format=csv&gid=1746689811. Downloads /templates/google-sheets.csv and /templates/google-sheets.xlsx.)\n         | {\"format\": \"jsonstat2\"}  (SSB/Eurostat; filter the query to a single series),\n\"transform\"?: [{\"op\": \"pct_change\"|\"diff\"|\"rolling_mean\"|\"rolling_sum\"|\"rolling_min\"|\"rolling_max\"|\"shift\", \"n\": int} | {\"op\": \"scale\", \"factor\": num}],\n\"keep\"?: \"all\"|\"last\"}. \"value_type\": \"text\" stores labels (no transforms; see Data policy). Dates like 2026-09-30, 2026M08, 2026Q2/2026K2, 2026 or unix time are normalised to ISO period-end dates; date-times (2026-10-05T14:00+02:00) count on the day as written. A single JSON value with no timestamp (omit \"date\", or a date path the body doesn't have) is dated with the day of the fetch: that is valid for a snapshot such as a price. A date that is present but not a date is an error. Do not invent a timestamp field.\nURLs and string bodies may use {today}, {yesterday}, {tomorrow}, {year}, {month}, {day} (UTC, filled in on every run) for sources that put the date in the URL.\nPrivate APIs: the URL, header values and string body may use {{secret:NAME}} for a secret the workspace owner stored (list_secrets shows the names and the one host each may be sent to); the value is filled in at fetch time and never shown, e.g. \"headers\": {\"Authorization\": \"Bearer {{secret:STRIPE_KEY}}\"}. A stored secret may be sent on GET, on a POST whose body is a GraphQL query, or on POST https://api.hubapi.com/crm/v3/objects/{deals,contacts,companies}/search only (HubSpot's read-only CRM search, JSON body; https://developers.hubspot.com/docs/api-reference/legacy/crm/search-the-crm). No other POST may carry a secret, and the secret is still sent only to the host it was saved for. If a needed secret is missing, ask the user to add it at /account under Data sources.",
      "inputSchema": {
        "properties": {
          "name": {
            "description": "optional label",
            "type": "string"
          },
          "spec": {
            "description": "job spec",
            "type": "object"
          }
        },
        "required": [
          "spec"
        ],
        "type": "object"
      },
      "name": "create_job"
    },
    {
      "description": "Create or replace a derived series, computed from other series every time it is read (always fresh, stores no points, counts toward the series limit). definition: {\"op\":\"add\"|\"subtract\"|\"multiply\"|\"divide\",\"a\":series,\"b\":series} (matched by date) | {\"op\":\"pct_change\"|\"diff\"|\"moving_average\"|\"rolling_min\"|\"rolling_max\"|\"shift\",\"a\":series,\"n\"?:1-1000} (shift: the value n points earlier) | {\"op\":\"scale\",\"a\":series,\"factor\":number} | {\"op\":\"resample\",\"a\":series,\"to\":\"M\"|\"Q\",\"how\"?:\"last\"|\"mean\"|\"sum\"|\"ffill\"} (to month/quarter ends; ffill also fills gaps up to today) | {\"op\":\"mean\"|\"sum\",\"inputs\":[2-20 series]} (dates all inputs have) | {\"op\":\"status\",\"a\":series,\"red\"?:[rule],\"yellow\"?:[rule]} → text labels red/yellow/green (red if any red rule holds, else yellow if any yellow rule, else green); rule: {\"series\"?:name (default a; read as of each date of a),\"above\"|\"below\":number,\"for\"?:1-24 (holds for the last k points)} or {\"all\":[2-10 rules]}; at most 10 rules | {\"op\":\"composite\",\"inputs\":[status series],\"min_inputs\"?:n} → 0-100 score (red 2, yellow 1, green 0) | {\"op\":\"count\",\"inputs\":[status series],\"value\":\"red\"|\"yellow\"|\"green\",\"share\"?:true (percent)}. Inputs may themselves be derived (up to 10 levels; one read may touch at most 200 stored series). Example: a yield-curve spread {\"op\":\"subtract\",\"a\":\"govt_10y\",\"b\":\"tbill_3m\"}.",
      "inputSchema": {
        "properties": {
          "definition": {
            "description": "definition",
            "type": "object"
          },
          "series": {
            "description": "name for the derived series",
            "type": "string"
          }
        },
        "required": [
          "series",
          "definition"
        ],
        "type": "object"
      },
      "name": "define_derived"
    },
    {
      "description": "Delete annotations of a series on the given dates, or all of them if dates is omitted.",
      "inputSchema": {
        "properties": {
          "dates": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "series": {
            "description": "name",
            "type": "string"
          }
        },
        "required": [
          "series"
        ],
        "type": "object"
      },
      "name": "delete_annotations"
    },
    {
      "description": "Delete a dashboard (series data is kept).",
      "inputSchema": {
        "properties": {
          "dashboard_id": {
            "description": "id",
            "type": "string"
          }
        },
        "required": [
          "dashboard_id"
        ],
        "type": "object"
      },
      "name": "delete_dashboard"
    },
    {
      "description": "Delete a job (its series data is kept).",
      "inputSchema": {
        "properties": {
          "job_id": {
            "description": "id",
            "type": "string"
          }
        },
        "required": [
          "job_id"
        ],
        "type": "object"
      },
      "name": "delete_job"
    },
    {
      "description": "Delete all points of a series, or remove a derived series' definition.",
      "inputSchema": {
        "properties": {
          "series": {
            "description": "name",
            "type": "string"
          }
        },
        "required": [
          "series"
        ],
        "type": "object"
      },
      "name": "delete_series"
    },
    {
      "description": "Read one integration playbook by id: what to track, the minimum credential and scopes, the exact tlly job, and gotchas. google-sheets and json-api also return a template field (Make a copy, public CSV, downloads). Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/copy. Public CSV https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/export?format=csv&gid=1746689811. Downloads /templates/google-sheets.csv and /templates/google-sheets.xlsx. Public: no API key required. Call this before inventing an API URL or scope.",
      "inputSchema": {
        "properties": {
          "id": {
            "description": "skill id from list_integration_skills, e.g. stripe or google-sheets",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "name": "get_integration_skill"
    },
    {
      "description": "Read the latest points of a series.",
      "inputSchema": {
        "properties": {
          "limit": {
            "type": "integer"
          },
          "series": {
            "description": "name",
            "type": "string"
          }
        },
        "required": [
          "series"
        ],
        "type": "object"
      },
      "name": "get_series"
    },
    {
      "description": "Health check: failing jobs, stale or empty panels, dashboards, storage usage. Call this to verify everything works.",
      "inputSchema": {
        "properties": {},
        "required": [],
        "type": "object"
      },
      "name": "get_status"
    },
    {
      "description": "Storage usage against quotas (data points = values + annotations, series, dashboards, jobs) and the largest series. Check before large writes.",
      "inputSchema": {
        "properties": {},
        "required": [],
        "type": "object"
      },
      "name": "get_usage"
    },
    {
      "description": "Fetch a job spec once and save the result, without scheduling it or using a job slot (\"cron\" is optional and ignored). Use for one-off dashboards whose data doesn't need to update; points count toward the data quota. Job spec: {\"series\": name, \"cron\": \"m h dom mon dow\" (UTC, at most every 5 min),\n\"request\": {\"url\", \"method\"?: \"GET\"|\"POST\", \"headers\"?: {}, \"body\"?: obj|str,\n            \"pages\"?: {\"param\": \"page\", \"start\"?: 0, \"max\"?: 1-10} (json with items: bump a query parameter until an empty items array)\n                     | {\"cursor\": \"paging.next.after\", \"into\": \"after\", \"max\"?: 1-10} (copy that JSON path from each response into the JSON body; stop when it is missing. A cursor that remains after max pages is an error, so a sum is never a partial total. HubSpot CRM search uses this. A json aggregate with no request.pages fails when the body still has another page, paging.next.after, has_more true, links.next, pageInfo.hasNextPage or another next cursor, instead of saving a partial total. Set pages. A response with no next marker is complete.)},\n\"extract\": {\"format\": \"json\", \"items\"?: \"path.to.array\", \"date\"?: \"field\", \"value\": \"field\", \"value_type\"?: \"number\"|\"text\",\n            \"aggregate\"?: \"count\"|\"sum\"|\"mean\"|\"min\"|\"max\" (records per date, e.g. hourly prices to a daily mean; needs items; all but count need value), \"period\"?: \"D\"|\"M\"|\"Q\",\n            \"where\"?: [{\"field\": \"path\", \"equals\"|\"not_equals\": value}] (up to 5, with aggregate). Omit \"date\" with aggregate to snapshot every matching record as one point on the day of the fetch. String numbers (HubSpot properties.amount) are summed.}\n         | {\"format\": \"json_columns\", \"dates\": \"path.to.array\", \"values\": \"path.to.array\", \"value_type\"?} (two parallel arrays, e.g. Yahoo chart: timestamp + close)\n         | {\"format\": \"csv\", \"date\"?: \"COLUMN\", \"value\": \"COLUMN\", \"delimiter\"?, \"value_type\"?} (omit date to stamp every row with the fetch day; one value per day, so the last row wins. That does not sum the rows the way a JSON aggregate that omits date does. A history needs a Date column. Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/copy. Public CSV https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/export?format=csv&gid=1746689811. Downloads /templates/google-sheets.csv and /templates/google-sheets.xlsx.)\n         | {\"format\": \"jsonstat2\"}  (SSB/Eurostat; filter the query to a single series),\n\"transform\"?: [{\"op\": \"pct_change\"|\"diff\"|\"rolling_mean\"|\"rolling_sum\"|\"rolling_min\"|\"rolling_max\"|\"shift\", \"n\": int} | {\"op\": \"scale\", \"factor\": num}],\n\"keep\"?: \"all\"|\"last\"}. \"value_type\": \"text\" stores labels (no transforms; see Data policy). Dates like 2026-09-30, 2026M08, 2026Q2/2026K2, 2026 or unix time are normalised to ISO period-end dates; date-times (2026-10-05T14:00+02:00) count on the day as written. A single JSON value with no timestamp (omit \"date\", or a date path the body doesn't have) is dated with the day of the fetch: that is valid for a snapshot such as a price. A date that is present but not a date is an error. Do not invent a timestamp field.\nURLs and string bodies may use {today}, {yesterday}, {tomorrow}, {year}, {month}, {day} (UTC, filled in on every run) for sources that put the date in the URL.\nPrivate APIs: the URL, header values and string body may use {{secret:NAME}} for a secret the workspace owner stored (list_secrets shows the names and the one host each may be sent to); the value is filled in at fetch time and never shown, e.g. \"headers\": {\"Authorization\": \"Bearer {{secret:STRIPE_KEY}}\"}. A stored secret may be sent on GET, on a POST whose body is a GraphQL query, or on POST https://api.hubapi.com/crm/v3/objects/{deals,contacts,companies}/search only (HubSpot's read-only CRM search, JSON body; https://developers.hubspot.com/docs/api-reference/legacy/crm/search-the-crm). No other POST may carry a secret, and the secret is still sent only to the host it was saved for. If a needed secret is missing, ask the user to add it at /account under Data sources.",
      "inputSchema": {
        "properties": {
          "spec": {
            "description": "job spec",
            "type": "object"
          }
        },
        "required": [
          "spec"
        ],
        "type": "object"
      },
      "name": "import_once"
    },
    {
      "description": "Read the latest annotations of a series.",
      "inputSchema": {
        "properties": {
          "limit": {
            "type": "integer"
          },
          "series": {
            "description": "name",
            "type": "string"
          }
        },
        "required": [
          "series"
        ],
        "type": "object"
      },
      "name": "list_annotations"
    },
    {
      "description": "List a dashboard's saved versions (newest first) with which one is live and a members-only preview URL for each.",
      "inputSchema": {
        "properties": {
          "dashboard_id": {
            "description": "id",
            "type": "string"
          }
        },
        "required": [
          "dashboard_id"
        ],
        "type": "object"
      },
      "name": "list_dashboard_versions"
    },
    {
      "description": "List dashboards with their URLs.",
      "inputSchema": {
        "properties": {},
        "required": [],
        "type": "object"
      },
      "name": "list_dashboards"
    },
    {
      "description": "List integration setup playbooks (Stripe, Shopify, Sheets, and the other sources tlly can read). A spreadsheet starts from the KPI template. Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/copy. Public CSV https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/export?format=csv&gid=1746689811. Downloads /templates/google-sheets.csv and /templates/google-sheets.xlsx. Public: no API key required.",
      "inputSchema": {
        "properties": {},
        "required": [],
        "type": "object"
      },
      "name": "list_integration_skills"
    },
    {
      "description": "List scheduled fetch jobs with their last run status and any error message.",
      "inputSchema": {
        "properties": {},
        "required": [],
        "type": "object"
      },
      "name": "list_jobs"
    },
    {
      "description": "List the names of the workspace's stored secrets (API keys for private sources) and the host each may be sent to. Use one in a job's url, headers or body as {{secret:NAME}}.",
      "inputSchema": {
        "properties": {},
        "required": [],
        "type": "object"
      },
      "name": "list_secrets"
    },
    {
      "description": "List all series with their type (number or text), point counts and date ranges; text series also list their values.",
      "inputSchema": {
        "properties": {},
        "required": [],
        "type": "object"
      },
      "name": "list_series"
    },
    {
      "description": "Make a saved version of a dashboard the live one (also to roll back). The dashboard keeps its URL and visibility.",
      "inputSchema": {
        "properties": {
          "dashboard_id": {
            "description": "id",
            "type": "string"
          },
          "version": {
            "description": "version number from list_dashboard_versions or update_dashboard",
            "type": "integer"
          }
        },
        "required": [
          "dashboard_id",
          "version"
        ],
        "type": "object"
      },
      "name": "publish_dashboard_version"
    },
    {
      "description": "Write dated values to a series (upsert by date). Use when you compute data yourself. Values are numbers, or short text labels for a text series (e.g. a rating or stance; see Data policy). A series' first write fixes its type.",
      "inputSchema": {
        "properties": {
          "points": {
            "description": "[{\"date\":\"2026-09-30\",\"value\":4.6}] or [{\"date\":\"2026-09-30\",\"value\":\"AA+\"}]",
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "series": {
            "description": "series name, e.g. household_credit_yoy",
            "type": "string"
          }
        },
        "required": [
          "series",
          "points"
        ],
        "type": "object"
      },
      "name": "push_points"
    },
    {
      "description": "Give a dashboard a new public URL and disable the old one at once (e.g. if a link leaked). Spec and data are unchanged.",
      "inputSchema": {
        "properties": {
          "dashboard_id": {
            "description": "id",
            "type": "string"
          }
        },
        "required": [
          "dashboard_id"
        ],
        "type": "object"
      },
      "name": "rotate_dashboard_url"
    },
    {
      "description": "Run a scheduled fetch job immediately, store any new points, and return the run result.",
      "inputSchema": {
        "properties": {
          "job_id": {
            "description": "id",
            "type": "string"
          }
        },
        "required": [
          "job_id"
        ],
        "type": "object"
      },
      "name": "run_job_now"
    },
    {
      "description": "Set or clear the workspace webhook URL for job-failure and dashboard status alerts (pass an empty url to clear). When events fire, tlly POSTs JSON to that URL (Slack and Discord incoming webhooks get a short text payload instead). Events include job_failed, status_changed (keep panel \"id\" fields in specs so alerts stay attached), and threshold_crossed. Email alerts work without a webhook. Caps: 12 emails and 60 webhooks per account per hour; the same panel is not alerted twice within an hour.",
      "inputSchema": {
        "properties": {
          "url": {
            "description": "https URL, or empty to clear",
            "type": "string"
          }
        },
        "required": [],
        "type": "object"
      },
      "name": "set_alert_webhook"
    },
    {
      "description": "Set when one dashboard panel sends alert email and/or a webhook (colour change, threshold cross, or off). trigger: change (default), red, notgreen, above, below, or off; above/below need limit. notify: both, email, or webhook. panel is the 0-based index in the live spec. Saving can send immediately if a limit was just crossed (unless this panel alerted within the last hour). Later sends happen after scheduled fetches. Default change+both removes a custom rule. Does not change colour lines or save a dashboard version.",
      "inputSchema": {
        "properties": {
          "dashboard_id": {
            "description": "id",
            "type": "string"
          },
          "limit": {
            "description": "required for above and below",
            "type": "number"
          },
          "notify": {
            "description": "both, email or webhook",
            "type": "string"
          },
          "panel": {
            "description": "0-based panel index",
            "type": "integer"
          },
          "trigger": {
            "description": "change, red, notgreen, above, below or off",
            "type": "string"
          }
        },
        "required": [
          "dashboard_id",
          "panel"
        ],
        "type": "object"
      },
      "name": "set_panel_alert"
    },
    {
      "description": "Configure when tlly sends stale-data alert email and webhooks because a series stopped updating. after: auto (from the job schedule and point spacing, at least two days), off, hours, or a window like 48h or 7d. Use dashboard_id for every series on a dashboard, series for one series, or both for one series on one dashboard. One email and one webhook when it goes quiet; another when a newer point arrives. Counts toward the hourly caps.",
      "inputSchema": {
        "properties": {
          "after": {
            "description": "auto, off, 48h, 7d, or a number of hours",
            "type": "string"
          },
          "dashboard_id": {
            "description": "optional dashboard id",
            "type": "string"
          },
          "series": {
            "description": "optional series name",
            "type": "string"
          }
        },
        "required": [],
        "type": "object"
      },
      "name": "set_stale_alert"
    },
    {
      "description": "Dry-run a job spec: fetch, extract and transform, return a preview. Saves nothing. Job spec: {\"series\": name, \"cron\": \"m h dom mon dow\" (UTC, at most every 5 min),\n\"request\": {\"url\", \"method\"?: \"GET\"|\"POST\", \"headers\"?: {}, \"body\"?: obj|str,\n            \"pages\"?: {\"param\": \"page\", \"start\"?: 0, \"max\"?: 1-10} (json with items: bump a query parameter until an empty items array)\n                     | {\"cursor\": \"paging.next.after\", \"into\": \"after\", \"max\"?: 1-10} (copy that JSON path from each response into the JSON body; stop when it is missing. A cursor that remains after max pages is an error, so a sum is never a partial total. HubSpot CRM search uses this. A json aggregate with no request.pages fails when the body still has another page, paging.next.after, has_more true, links.next, pageInfo.hasNextPage or another next cursor, instead of saving a partial total. Set pages. A response with no next marker is complete.)},\n\"extract\": {\"format\": \"json\", \"items\"?: \"path.to.array\", \"date\"?: \"field\", \"value\": \"field\", \"value_type\"?: \"number\"|\"text\",\n            \"aggregate\"?: \"count\"|\"sum\"|\"mean\"|\"min\"|\"max\" (records per date, e.g. hourly prices to a daily mean; needs items; all but count need value), \"period\"?: \"D\"|\"M\"|\"Q\",\n            \"where\"?: [{\"field\": \"path\", \"equals\"|\"not_equals\": value}] (up to 5, with aggregate). Omit \"date\" with aggregate to snapshot every matching record as one point on the day of the fetch. String numbers (HubSpot properties.amount) are summed.}\n         | {\"format\": \"json_columns\", \"dates\": \"path.to.array\", \"values\": \"path.to.array\", \"value_type\"?} (two parallel arrays, e.g. Yahoo chart: timestamp + close)\n         | {\"format\": \"csv\", \"date\"?: \"COLUMN\", \"value\": \"COLUMN\", \"delimiter\"?, \"value_type\"?} (omit date to stamp every row with the fetch day; one value per day, so the last row wins. That does not sum the rows the way a JSON aggregate that omits date does. A history needs a Date column. Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/copy. Public CSV https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/export?format=csv&gid=1746689811. Downloads /templates/google-sheets.csv and /templates/google-sheets.xlsx.)\n         | {\"format\": \"jsonstat2\"}  (SSB/Eurostat; filter the query to a single series),\n\"transform\"?: [{\"op\": \"pct_change\"|\"diff\"|\"rolling_mean\"|\"rolling_sum\"|\"rolling_min\"|\"rolling_max\"|\"shift\", \"n\": int} | {\"op\": \"scale\", \"factor\": num}],\n\"keep\"?: \"all\"|\"last\"}. \"value_type\": \"text\" stores labels (no transforms; see Data policy). Dates like 2026-09-30, 2026M08, 2026Q2/2026K2, 2026 or unix time are normalised to ISO period-end dates; date-times (2026-10-05T14:00+02:00) count on the day as written. A single JSON value with no timestamp (omit \"date\", or a date path the body doesn't have) is dated with the day of the fetch: that is valid for a snapshot such as a price. A date that is present but not a date is an error. Do not invent a timestamp field.\nURLs and string bodies may use {today}, {yesterday}, {tomorrow}, {year}, {month}, {day} (UTC, filled in on every run) for sources that put the date in the URL.\nPrivate APIs: the URL, header values and string body may use {{secret:NAME}} for a secret the workspace owner stored (list_secrets shows the names and the one host each may be sent to); the value is filled in at fetch time and never shown, e.g. \"headers\": {\"Authorization\": \"Bearer {{secret:STRIPE_KEY}}\"}. A stored secret may be sent on GET, on a POST whose body is a GraphQL query, or on POST https://api.hubapi.com/crm/v3/objects/{deals,contacts,companies}/search only (HubSpot's read-only CRM search, JSON body; https://developers.hubspot.com/docs/api-reference/legacy/crm/search-the-crm). No other POST may carry a secret, and the secret is still sent only to the host it was saved for. If a needed secret is missing, ask the user to add it at /account under Data sources.",
      "inputSchema": {
        "properties": {
          "spec": {
            "description": "job spec",
            "type": "object"
          }
        },
        "required": [
          "spec"
        ],
        "type": "object"
      },
      "name": "test_job"
    },
    {
      "description": "Replace a dashboard's spec (URL stays the same). Every update is saved as a numbered version. With \"draft\": true it is saved without going live: viewers keep seeing the live version, members can preview the draft at the returned preview_url, and publish_dashboard_version makes it live. Use drafts for larger changes and publish when the user confirms. Dashboard spec: {\"title\": str, \"description\"?: str, \"range\"?: \"1m\"|\"3m\"|\"6m\"|\"ytd\"|\"1y\"|\"2y\"|\"5y\"|\"10y\"|\"max\", \"theme\"?: \"auto\"|\"light\"|\"dark\" (default mode; auto follows the viewer's system and viewers can switch), \"layout\"?: \"auto\"|\"overview\"|\"detail\" (only the default for panels without \"display\": overview makes them tiles, detail makes them charts, auto makes them tiles above 8 panels), \"visibility\"?: \"public\"|\"private\" (default public: anyone with the link; private: only members of this account signed in to tlly.dev, so embeds and shared links stop working for others), \"panels\": [{\"title\"?: str,\n\"series\": \"name\" | [\"a\",\"b\"], \"type\"?: \"line\"|\"area\"|\"bar\"|\"stat\"|\"gauge\"|\"bargauge\"|\"pie\"|\"heatmap\"|\"table\", \"unit\"?: str, \"note\"?: str, \"stale_after_days\"?: num,\n\"thresholds\"?: {\"red_above\"?, \"yellow_above\"?, \"yellow_below\"?, \"red_below\"?}, \"range\"?: (as above, overrides the dashboard's),\n\"section\"?: str (starts a new titled section at this panel), \"views\"?: bool (show the Level / Y/Y % / Index buttons; default: shown unless the unit is already a rate like %, y/y or pp), \"display\"?: \"tile\"|\"chart\" (tile: a compact number with a sparkline, click for the full chart; chart: a card with the full chart; mix them freely, e.g. tiles for the many supporting numbers and charts for the few that need their history), \"headline\"?: bool (show as a big number at the top of the dashboard, under a status strip; use for the 3-6 most important), \"width\"?: \"half\"|\"full\", \"scale\"?: \"linear\"|\"log\",\n\"id\"?: str (stable panel id, e.g. pn_abc123XYZ012; keep existing ids when editing so alerts stay attached),\n\"right_axis\"?: [series names] (line, area and bar charts: draw these series against a second y axis on the right, for series of different sizes or units; at least one stays on the left), \"right_unit\"?: str (unit of the right axis; \"unit\" is the left's), \"left_label\"?/\"right_label\"?: str (≤40 chars, a title written vertically along the left / right y axis, e.g. \"Policy rate\"; right_label needs right_axis),\n\"view\"?: \"level\"|\"yoy\"|\"index\" (initial view; viewers can switch), \"source\"?: str (shown as \"Source: …\"; defaults to the job's host),\n\"labels\"?: [display name per series], \"status_series\"?: name (a red/yellow/green text series that sets the panel colour),\n\"compare\"?: \"prev\"|\"3m\"|\"1y\" (what the change is measured against), \"period\"?: \"D\"|\"M\"|\"Q\" (show dates as 2026-06 or 2026Q2), \"value_names\"?: {\"green\": \"Hold\", …} (display names for text values)}],\n\"badge\"?: \"worst\" (default: the header badge shows the worst panel) | \"off\" | panel index (0-based; the badge follows that panel),\n\"shade\"?: [{\"from\": date, \"to\": date, \"label\"?: str}] (up to 20 shaded periods on every chart)}.\n\"type\": \"table\" shows one row per series (up to 20): label, latest value, date and a status dot; its \"status_series\" is a list with one entry per row (\"\" for none).\n\"area\" is a line chart filled underneath; \"gauge\" is a dial of the latest value with the threshold zones, and \"bargauge\" a horizontal bar per series (up to 20, coloured by thresholds), both scaled by \"min\"?/\"max\"?: num (default 0 to a bit above the largest value or threshold); \"pie\" is a donut of the series' latest values (shares of a whole, up to 20); \"heatmap\" is a calendar of the first series' daily values (good for activity per day).\nStatus colour is computed from the latest value of the panel's first series; a series of red/yellow/green labels (e.g. a derived status) colours itself.\nNotes may use {latest}, {date}, {period} and {change}, filled in from the panel's latest value. Annotations on a panel's series appear as markers.\nViewers can pick a time range (1M to Max, year to date, or a custom from-to period), zoom, switch views and download CSV; ?embed=1 hides the page chrome and &panel=N shows one panel.\nText limits: title ≤120, description ≤500, panel title ≤120, unit ≤20, note ≤280, section and source ≤80 characters, single line, plain text.",
      "inputSchema": {
        "properties": {
          "dashboard_id": {
            "description": "id",
            "type": "string"
          },
          "draft": {
            "description": "save as a draft version without changing the live dashboard. Chat always drafts, even if this is false.",
            "type": "boolean"
          },
          "note": {
            "description": "optional short description of this version (≤200 characters)",
            "type": "string"
          },
          "replace_thresholds": {
            "description": "set true only after the user agrees to drop or change an existing colour line",
            "type": "boolean"
          },
          "spec": {
            "description": "dashboard spec",
            "type": "object"
          }
        },
        "required": [
          "dashboard_id",
          "spec"
        ],
        "type": "object"
      },
      "name": "update_dashboard"
    },
    {
      "description": "Replace a job's spec, or pause it with enabled=false. Job spec: {\"series\": name, \"cron\": \"m h dom mon dow\" (UTC, at most every 5 min),\n\"request\": {\"url\", \"method\"?: \"GET\"|\"POST\", \"headers\"?: {}, \"body\"?: obj|str,\n            \"pages\"?: {\"param\": \"page\", \"start\"?: 0, \"max\"?: 1-10} (json with items: bump a query parameter until an empty items array)\n                     | {\"cursor\": \"paging.next.after\", \"into\": \"after\", \"max\"?: 1-10} (copy that JSON path from each response into the JSON body; stop when it is missing. A cursor that remains after max pages is an error, so a sum is never a partial total. HubSpot CRM search uses this. A json aggregate with no request.pages fails when the body still has another page, paging.next.after, has_more true, links.next, pageInfo.hasNextPage or another next cursor, instead of saving a partial total. Set pages. A response with no next marker is complete.)},\n\"extract\": {\"format\": \"json\", \"items\"?: \"path.to.array\", \"date\"?: \"field\", \"value\": \"field\", \"value_type\"?: \"number\"|\"text\",\n            \"aggregate\"?: \"count\"|\"sum\"|\"mean\"|\"min\"|\"max\" (records per date, e.g. hourly prices to a daily mean; needs items; all but count need value), \"period\"?: \"D\"|\"M\"|\"Q\",\n            \"where\"?: [{\"field\": \"path\", \"equals\"|\"not_equals\": value}] (up to 5, with aggregate). Omit \"date\" with aggregate to snapshot every matching record as one point on the day of the fetch. String numbers (HubSpot properties.amount) are summed.}\n         | {\"format\": \"json_columns\", \"dates\": \"path.to.array\", \"values\": \"path.to.array\", \"value_type\"?} (two parallel arrays, e.g. Yahoo chart: timestamp + close)\n         | {\"format\": \"csv\", \"date\"?: \"COLUMN\", \"value\": \"COLUMN\", \"delimiter\"?, \"value_type\"?} (omit date to stamp every row with the fetch day; one value per day, so the last row wins. That does not sum the rows the way a JSON aggregate that omits date does. A history needs a Date column. Make a copy https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/copy. Public CSV https://docs.google.com/spreadsheets/d/1Ty5X3LkY2unxTNGbkq36qDzuuYxQMsuywaTNyzL_pac/export?format=csv&gid=1746689811. Downloads /templates/google-sheets.csv and /templates/google-sheets.xlsx.)\n         | {\"format\": \"jsonstat2\"}  (SSB/Eurostat; filter the query to a single series),\n\"transform\"?: [{\"op\": \"pct_change\"|\"diff\"|\"rolling_mean\"|\"rolling_sum\"|\"rolling_min\"|\"rolling_max\"|\"shift\", \"n\": int} | {\"op\": \"scale\", \"factor\": num}],\n\"keep\"?: \"all\"|\"last\"}. \"value_type\": \"text\" stores labels (no transforms; see Data policy). Dates like 2026-09-30, 2026M08, 2026Q2/2026K2, 2026 or unix time are normalised to ISO period-end dates; date-times (2026-10-05T14:00+02:00) count on the day as written. A single JSON value with no timestamp (omit \"date\", or a date path the body doesn't have) is dated with the day of the fetch: that is valid for a snapshot such as a price. A date that is present but not a date is an error. Do not invent a timestamp field.\nURLs and string bodies may use {today}, {yesterday}, {tomorrow}, {year}, {month}, {day} (UTC, filled in on every run) for sources that put the date in the URL.\nPrivate APIs: the URL, header values and string body may use {{secret:NAME}} for a secret the workspace owner stored (list_secrets shows the names and the one host each may be sent to); the value is filled in at fetch time and never shown, e.g. \"headers\": {\"Authorization\": \"Bearer {{secret:STRIPE_KEY}}\"}. A stored secret may be sent on GET, on a POST whose body is a GraphQL query, or on POST https://api.hubapi.com/crm/v3/objects/{deals,contacts,companies}/search only (HubSpot's read-only CRM search, JSON body; https://developers.hubspot.com/docs/api-reference/legacy/crm/search-the-crm). No other POST may carry a secret, and the secret is still sent only to the host it was saved for. If a needed secret is missing, ask the user to add it at /account under Data sources.",
      "inputSchema": {
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "job_id": {
            "description": "id",
            "type": "string"
          },
          "spec": {
            "description": "job spec",
            "type": "object"
          }
        },
        "required": [
          "job_id",
          "spec"
        ],
        "type": "object"
      },
      "name": "update_job"
    }
  ]
}

What the leaf hashes

{"body":"a5b7cb2712290b4046b1e942492cbdfe29b04280f4f5a15f8be3c3a3a786f95e","id":1500706,"kind":"mcp.tools","method":"probe.mcp/1","observed_us":1793467250408974,"prev":1478986,"subject":"ef48ad6b-704a-4908-b2d4-2394394f5c32","subject_kind":"endpoint"}

Proof

Fold these into the leaf in order, hashing each sorted pair with keccak256, to reach the root of (246033 rows).

  1. 0x1a33b785f72a776d63fd81b5db5582c83c6161b307c6191343819bf2fd8d0fe6
  2. 0xc0b41805a18b3b00201dad5ff01d856eb81b4a78fbc96ee9e7503da03b24269d
  3. 0x4f8b4e319fae45ecec6e10a2893a87f767affd692ce3266e18dc252701cd5975
  4. 0xfa16ee6d39ebd33eefff4b20f4428fe7181c4fc4e429fb14f91cf2de1a57c232
  5. 0xe0c2b3cc58a008fe1c722ad39feb1b3938d12856fb9a544bcbc1c61c395eb28f
  6. 0x717b30fb82030b019c8863ccefaa1d66ca8f34405c0ba559d5b398cdc8631205
  7. 0x99e5287ae66d72745c624debc4dd45fa6bd699db88527139796f01523bea6247
  8. 0xff50aa8c7a4e933ad505b885a6d48ad829de2a7c8141696aca2c0df268a008d7
  9. 0x4e03861515947b110da2120cf11ff356b3f5981c7567227f40c2d694ad647615
  10. 0x581eeba73d07416a9afc5aac16c2892cc9673b14efbc3a1fd590299b1b7e5681
  11. 0xa118369db22e0e00b6aa4648d24fe2fed20c310b1ab049eae0258e48f4f3b8e4
  12. 0x8a696807387da79797692f50fd71b3e9f20bc764b5b3d60a7d7ede651a9e7cd5
  13. 0xf1614e76e51b9462cf7a91e5141af61bc7debba634c8798728c24a84f197b6aa
  14. 0x9ceb0cca7e6a4252fd4bc9968554fbb343590c4ea20f4c2a07778fe411162b49
  15. 0x95ec49fc7f2c7be30422eaa1d0d6036836dd63ff46a8ed4027258fcf3eb8cf24
  16. 0x2bca4aa3c59fe9209016c64bd8b706423477e3407201a00165afde9abf6fee2b
  17. 0x37f13582abb3c002d51c2f190c312129cbc3a39754618fee2c75c2441bb1eb94
  18. 0x59f83f7228f07febe2780660e422e171b0359b8aa031ee3be07e991118130c15
Day root
0x24463725d1c0d9894bd6123691d532547aaa79f0762c0e45aa6a68acf1fd71ab
Chained
0x7d4824ff313dee7252a46f9d5c37b452851deb6c9f24a40c2f74ee0e53c82980
Published
On Arc Testnet, block 70093156: 0xedb7b832f4ebf6781b5ecd2ed241c435b862f4d3761541fa31adc1831b8c44a0

How to check it · This row as JSON