Docs / Connect your data
Any other source
A database, a warehouse, a nightly job or another service: pick the way to send its count.
In short: pick the row that matches where your number lives. Each way sends a count, and the first count starts the watch. If your tool can call a link, a Webhook URL is the simplest.
Use a recipe when one fits. Otherwise use one of these general methods: every source fits one, and none needs a new endpoint.
| Your source | Method |
|---|---|
| A count you can query: a SQL database (Postgres, MySQL, MariaDB), a warehouse or log store (ClickHouse, Tinybird), another service's API | Scheduled query → ingest. A cron job or a CI schedule runs every 5 minutes. It counts per 5-minute window over the last 2 hours, every 5-minute window, zeros included (generate_series in SQL, WITH FILL in ClickHouse), up to the last complete one, and sends them with set to an explicit watch: a re-send replaces a 5-minute window (never doubles it), late rows are picked up, and a stopped job raises the no-data alert. Written out and run in CI: Watch a ClickHouse query, Watch a Postgres query. |
| Your own code sees each event (an app, a worker, a job) | Client library: npm install countwatch (Node 20.19+, Bun, Deno), then import { CountWatch } from "countwatch"; const cw = new CountWatch();, or pip install countwatch (Python 3.9+), then from countwatch import CountWatch; cw = CountWatch(); then cw.count("orders") where the event happens. It reads COUNTWATCH_API_KEY and defaults to https://api.countwatch.com; for this host set COUNTWATCH_API_URL=https://countwatch.com. No package manager? Paste the counter file from the app-counter recipe: the same count() calls, sent every 5 minutes. Each sums per 5 minutes, sends a 0 for each empty 5-minute window of every metric it has seen (an explicit watch: a stopped process raises the no-data alert), sends at the account's interval (send_interval_seconds in GET /v1/status, read again after each send) and retries without double counts. Declare metrics at start (metrics) to get zeros before their first event. Serverless (no timer): call flush() before returning; the watch is then a push watch. |
| A tool that sends webhooks or can call a URL (forms, no-code tools, CI) | Webhook URL: made in the dashboard (Senders (Connect), https://countwatch.com/dashboard/connections?tab=senders) and pasted into the tool; each hit adds 1 (or ?n=); a delivery id (?id= or the sender's delivery header) counts a retry once. It makes a push watch: the link is called as things happen, so a 5-minute window with no hits is zero. |
| A shell script or cron job that finishes something often (every few minutes or more) | Webhook URL with curl at the end of the script: curl -fsS "$COUNTWATCH_WEBHOOK_URL" (a push watch; npx countwatch send <metric> [n] --tag key:value comes with the npm package and sends with COUNTWATCH_API_KEY instead). A push watch raises the no-data alert only with expected_interval_minutes set (PATCH /v1/watches/{watch_id} with an admin key, the watch_id from GET /v1/status; over MCP manage update_watch): set it a little above the job's normal gap. |
| Past data: the scheduled query over the last 89 days, or an export file (CSV) | Import once: count per 5 minutes, every 5-minute window (an empty one is a 0), with UTC start times, and send it with POST /v1/import (resolution: "5m"; the API takes up to 90 days back, and the examples take 89 days so the first 5-minute window is still inside the 90 days when the request arrives), ending where the scheduled sends begin. The API stops an import at a watch's first live 5-minute window, so it never overwrites live counts (the add-0 point that creates a push watch does not count). "1h" or "1d" only for a source that keeps nothing finer (they are spread evenly over 5-minute windows). Only the times of single events (sign-up or order timestamps)? Send a watches entry {"metric": …, "events": ["2026-08-01T19:04:44Z", …]} instead of counts (up to 10,000 per entry): each adds 1 to its 5-minute window, and the empty 5-minute windows up to now are filled with 0. |
Rules for any method:
- Create the watch first, with its source:
POST /v1/watches(MCPmanage create_watch) with"source"push or explicit makes it before any data (until its first countPATCH /v1/watches/{watch_id}{"source": …}still changes it; after it the source is fixed); otherwise an explicit watch by its firstsetpoints, a push watch by one point{"metric": "…", "add": 0}with"source": "push". - One metric per thing counted (
orders,signups); tags only for a few values each (every combination is a watch, within the plan's watch limit). - Batch scheduled sends: one request carries up to 10,000 points.
- A sender that sends every 5 minutes uses about 8,640 requests a month: Free's 30,000 cover 3 such senders; an e-mail warns at 80%.
- Keys and Webhook URLs are secrets. Key: run
npx countwatch initin the project; the person approves it in the browser and it is saved as COUNTWATCH_API_KEY (--printpipes it to a host's secrets). Never read, print or ask for a key, a signing secret, a Webhook URL or a Slack URL in chat. A Webhook URL (COUNTWATCH_WEBHOOK_URL, under Senders (Connect)) goes into tools that call a URL: an agent gets it only through MCPwebhook_url(the person approves) to paste into the tool itself. Code reads those names. Keys can also be made under Settings (https://countwatch.com/dashboard/settings). - Then the same checks as every recipe:
GET /v1/status(an explicit watch'smissing_buckets_24hstays 0), the import'sstatus: done, a test alert.
The scheduled query's request (ts is each 5-minute window's start, ISO 8601 or Unix milliseconds, at most 6 hours back):
curl -sX POST https://countwatch.com/v1/ingest -H "Authorization: Bearer $COUNTWATCH_API_KEY" -H "Content-Type: application/json" \
-d '{"recipe":"scheduled-query","points":[{"metric":"orders","ts":"<bucket start>","set":7},{"metric":"orders","ts":"<next bucket start>","set":0}]}'
For agents: this page as Markdown.