Track Custom API Integrations usage in onWatch with local JSONL ingestion.
Ingest local JSONL files to monitor Custom API Integrations usage in onWatch. This is not for subscription or quota tracking, but for logging token usage in custom scripts and programs that make API calls. Wrap your integrations with telemetry to record per-call token usage, cost, and latency data, and track everything in onWatch.
- onWatch with the Custom API Integrations backend enabled
- A script or automation that already calls a supported provider API
- Ability to write a JSONL file locally
Supported v1 providers:
anthropicopenaimistralopenroutergemini
This list is just getting started... feel free to add more providers as you need them!
- Your script calls the provider API.
- Your script reads the usage fields from the API response.
- Your script appends one normalised JSON object per line to a file in
~/.onwatch/api-integrations/. - onWatch tails
*.jsonlfiles in that directory and stores the events in SQLite.
The source files are just the ingest input. The canonical persisted data lives in ~/.onwatch/data/onwatch.db.
- API Integrations directory:
~/.onwatch/api-integrations - Database:
~/.onwatch/data/onwatch.db - Log file:
~/.onwatch/data/.onwatch.log
In containers, the default API Integrations directory is /data/api-integrations.
Custom API Integrations ingestion is enabled by default.
Optional environment variables:
ONWATCH_API_INTEGRATIONS_ENABLED=true
ONWATCH_API_INTEGRATIONS_DIR=~/.onwatch/api-integrations
ONWATCH_API_INTEGRATIONS_RETENTION=1440hIf you change ONWATCH_API_INTEGRATIONS_DIR, point your scripts and onWatch at the same directory.
Retention notes:
ONWATCH_API_INTEGRATIONS_RETENTIONcontrols how long ingested API Integrations events are kept in SQLite- default retention is
1440hwhich is 60 days - set
ONWATCH_API_INTEGRATIONS_RETENTION=0to disable database pruning - pruning applies only to the SQLite table, not to the source
.jsonlfiles
Write one JSON object per line.
Required fields:
{
"ts": "2026-04-03T12:00:00Z",
"integration": "notes-organiser",
"provider": "anthropic",
"model": "claude-3-7-sonnet",
"prompt_tokens": 1200,
"completion_tokens": 300
}Optional fields:
total_tokenscost_usdlatency_msaccountrequest_idmetadata
Full example:
{
"ts": "2026-04-03T12:00:00Z",
"integration": "notes-organiser",
"provider": "anthropic",
"account": "personal",
"model": "claude-3-7-sonnet",
"request_id": "req_123",
"prompt_tokens": 1200,
"completion_tokens": 300,
"total_tokens": 1500,
"cost_usd": 0.0123,
"latency_ms": 1840,
"metadata": {
"task": "weekly-meeting-notes"
}
}Notes:
tsmust be RFC3339 in UTC, for example2026-04-03T12:00:00Zprovidermust be one of the supported v1 provider namesmetadatamust be a JSON object if present- If
accountis omitted, onWatch stores it asdefault - If
total_tokensis omitted, onWatch computesprompt_tokens + completion_tokens
Python-first examples are included here:
examples/api_integrations/python/onwatch_api_integrations.pyexamples/api_integrations/python/anthropic_example.pyexamples/api_integrations/python/openai_example.pyexamples/api_integrations/python/mistral_example.pyexamples/api_integrations/python/openrouter_example.pyexamples/api_integrations/python/gemini_example.py
The helper in examples/api_integrations/python/onwatch_api_integrations.py appends normalised JSONL events to the API Integrations directory.
These are initial examples to show the general use-case, but the logic can be expanded to any API-driven custom integration you want.
Included example utilities currently include:
examples/api_integrations/python/generate_practice_dataset.py
You can also build your own wrapper around the helper and write any number of integration-specific JSONL files, as long as they end in .jsonl.
Once events are being ingested, open the API Integrations tab in the dashboard.
The dashboard shows:
- per-integration cards with request counts, token totals, providers, and optional cost
- all-time and recent usage insight panels
- a shared usage chart with metric modes for tokens per call, API calls, accumulated tokens, and cost
- ingest health, tailed files, and recent alerts
API Integrations can also be queried through the read-only backend API:
GET /api/api-integrations/currentGET /api/api-integrations/history?range=6hGET /api/api-integrations/health
Dashboard visibility is controlled through the normal settings API via api_integrations_visibility, but ingestion itself is controlled by ONWATCH_API_INTEGRATIONS_ENABLED.
Foreground mode is easiest for first-time verification:
onwatch --debugYou should see a log line showing that the API integrations ingester started.
Check that your script is writing JSONL events:
ls -la ~/.onwatch/api-integrations
tail -n 20 ~/.onwatch/api-integrations/*.jsonlEach API call should append one valid JSON line.
Check recently ingested API integrations usage events:
sqlite3 ~/.onwatch/data/onwatch.db "select integration_name, provider, account_name, model, prompt_tokens, completion_tokens, total_tokens, captured_at from api_integration_usage_events order by id desc limit 20;"Check ingest cursor state:
sqlite3 ~/.onwatch/data/onwatch.db "select source_path, offset_bytes, file_size, partial_line from api_integration_ingest_state;"Expected result:
api_integration_usage_eventscontains one row per ingested eventapi_integration_ingest_statecontains one row per tailed fileoffset_bytesincreases as the file grows
Check:
- onWatch is running
ONWATCH_API_INTEGRATIONS_ENABLEDis not set tofalse- your script writes into the same directory that onWatch is tailing
- the file name ends with
.jsonl - each line is valid JSON
Run:
tail -f ~/.onwatch/data/.onwatch.logonWatch skips malformed or schema-invalid lines and creates a system alert instead of stopping ingestion.
Check recent alerts:
sqlite3 ~/.onwatch/data/onwatch.db "select provider, alert_type, title, message, created_at from system_alerts where provider = 'api_integrations' order by id desc limit 20;"onWatch deduplicates ingested events using a derived fingerprint based on the source path and stable event fields.
This protects against:
- daemon restart
- file reread after truncation
- repeated scans of the same already-ingested lines
If you intentionally want two events, they must differ in at least one meaningful field such as timestamp or request id.
If you want to start a fresh source log for new events, move or rename the active .jsonl file and let your wrapper create a new one.
Notes:
- onWatch will treat the new file as a new ingest source
- previously ingested history remains in SQLite until you clear or replace the stored database
- rotating the source file changes future ingestion, but it does not erase existing chart history by itself
Custom API Integrations data is stored in separate SQLite tables from the existing subscription/quota tracking tables:
api_integration_usage_eventsapi_integration_ingest_state
This means Custom API Integrations telemetry is identifiable and queryable independently from provider quota snapshots and reset cycles.
Database retention behavior:
- onWatch automatically prunes old rows from
api_integration_usage_events - the pruning cutoff is controlled by
ONWATCH_API_INTEGRATIONS_RETENTION - the default is 60 days
- source
.jsonlfiles are not pruned or compacted by onWatch - if you want smaller source logs, rotate or remove the JSONL files manually