API & MCP
Command-line interface (CLI)
Install the HoneyLog CLI and read your traffic reports, bot activity and raw logs from a terminal or a script.
The HoneyLog CLI reads your traffic data from a terminal. It covers the same reports as the dashboard: traffic over time, top pages, bots and countries, and the individual requests. You can print them as a table, or pipe them into jq, a spreadsheet or a cron job.
It is a thin wrapper over the REST API. Every command calls one endpoint, the same one an MCP server tool calls. You sign in once with your browser, like an MCP connector, or give it a team API token. Either way it shares the same rate limit. Everything it does is read-only.
It is a single Python file with no dependencies. All it needs is Python 3.8 or newer, which macOS and most Linux distributions already include.
Installation
macOS and Linux
curl -fsSL https://honeylog.io/cli/honeylog.py --create-dirs -o ~/.local/bin/honeylog && chmod +x ~/.local/bin/honeylog
Then check it with honeylog --version. If the shell answers honeylog: command not found, then ~/.local/bin is not on your PATH. Add export PATH="$HOME/.local/bin:$PATH" to ~/.bashrc or ~/.zshrc and open a new terminal, or run the CLI as python3 ~/.local/bin/honeylog.
You can read the script in your browser before running it.
Windows
Invoke-WebRequest https://honeylog.io/cli/honeylog.py -OutFile honeylog.py
py honeylog.py --version
On Windows, write py honeylog.py wherever this page writes honeylog.
Updating
Run the installation command again. It replaces the file with the current version. honeylog --version tells you which version you have.
Setup
Sign in once with your browser:
honeylog login
The CLI opens HoneyLog in your browser. Sign in if you need to, choose the team the CLI should read, and click Allow access. Back in the terminal, the CLI confirms with logged in to https://honeylog.io until ….
You sign in once per computer, not once per terminal: the CLI saves the token, and every new terminal uses it. The login lasts 60 days. After that, the CLI tells you it has expired and you run honeylog login again. In the last week, it reminds you whenever you run a command in a terminal.
Only team owners and managers can sign in, the same people who can create API tokens. HoneyLog admins can also choose to read every site.
Where the login is saved
The token goes in credentials.json, in a folder only your user account can read:
| System | File |
|---|---|
| macOS and Linux | ~/.config/honeylog/credentials.json, or $XDG_CONFIG_HOME/honeylog/credentials.json |
| Windows | %APPDATA%\honeylog\credentials.json |
HONEYLOG_CONFIG_DIR sets a different folder. In HoneyLog, the token appears in Settings → API Tokens, MCP & CLI as Honeylog CLI on {computer} (CLI, authorized by {you}), with its expiry date. Deleting it there signs that computer out.
Over SSH or on a server
Where the CLI cannot open a browser (over SSH, or on a machine without a display), it prints the sign-in address instead. Open it in a browser on any computer and allow access. The browser then lands on an http://127.0.0.1:… page that does not load. Copy that address from the address bar and paste it in the terminal. honeylog login --no-browser works this way on purpose.
Signing out
honeylog logout
This revokes the token in HoneyLog and deletes it from this computer. A new honeylog login also revokes the token it replaces, so each computer keeps one.
Scripts, cron jobs and CI
A login expires, and a CI runner has no browser. For anything that runs on its own, use a team API token instead:
- In HoneyLog, select the team you want to read, then open Settings → API Tokens, MCP & CLI.
- Create a token with
sites.readfor sites and custom views. If you also want networks, give itnetworks.read, which includessites.read. - Copy the token while the dialog is on screen.
Then give it to the CLI through the HONEYLOG_TOKEN environment variable, which takes precedence over the login. Reading it from a prompt keeps it out of your shell history. This works in bash and zsh:
export HONEYLOG_TOKEN
printf 'Honeylog API token: '; read -rs HONEYLOG_TOKEN; echo
In PowerShell 7.1 or newer:
$env:HONEYLOG_TOKEN = Read-Host "Honeylog API token" -MaskInput
The variable lasts until you close the terminal. In a CI system, store the token as a secret and expose it as HONEYLOG_TOKEN. A token created by connecting an MCP client works here too, since it is the same kind of team token.
Verify the installation
honeylog sites
If the login works, you get every site, custom view and network it can read, each with the wireable identifier the other commands need:
type wireable name timezone
------- -------------------------------------------- ------------------ -----------
site site:9b2f6c1e-4d0a-4c55-8f0e-2a7d3b1c9e44 example.com Europe/Rome
view site:9b2f6c1e-4d0a-4c55-8f0e-2a7d3b1c9e44@12 example.com / Blog Europe/Rome
network network:4 All brands
If it doesn't, the CLI prints the reason and exits with a code that tells the cases apart:
| Exit code | Message | What to check |
|---|---|---|
2 |
not logged in |
Run honeylog login, or set HONEYLOG_TOKEN |
3 |
the login … expired |
The 60 days are over: run honeylog login again |
3 |
HTTP 401: Unauthenticated. |
The token was mistyped, or has been deleted. After a login, the CLI also says to run honeylog login again |
3 |
HTTP 403: The v1 API requires a team API token. |
The token is a personal token, not a team token |
3 |
HTTP 403: …subscription… or …blocked for your plan. |
The team's plan has no API access, or the subscription is inactive |
8 |
cannot reach https://honeylog.io |
Network, proxy or firewall |
Usage examples
# Find a site and its wireable
honeylog sites --search example.com
# The metric and dimension names that site accepts
honeylog fields site:SITE_UUID
# Pageviews and sessions, last 30 days vs the 30 days before
honeylog timeseries site:SITE_UUID --metrics views,sessions \
--range last_30_days --compare-range previous_period
# Which bots came yesterday, with the totals over all of them
honeylog breakdown site:SITE_UUID --dimension bot_name \
--range yesterday --with-totals
# Every URL GPTBot requested today, with its status, as CSV
honeylog --format csv raw-logs site:SITE_UUID --range today \
--columns datetime_second,full_url,status_code \
--where '[[["bot_name","=","GPTBot"]]]' --all > gptbot.csv
# Requests per minute over the last two hours
honeylog raw-logs-timeseries site:SITE_UUID --last 2h \
--granularity 1m
Filters can come from a file (@file) or from standard input (-):
cat > ai-crawlers.json <<'EOF'
[[["bot_name", "IN", ["GPTBot", "ClaudeBot", "PerplexityBot"]]]]
EOF
honeylog breakdown site:SITE_UUID --range last_7_days \
--where @ai-crawlers.json
When the output is piped, it is JSON by default, so it combines with jq:
# Top countries of the last 7 days, as tab-separated text
honeylog breakdown site:SITE_UUID --dimension country_code \
--range last_7_days \
| jq -r '.data[] | "\(.country_code)\t\(.views)"'
# One site group per command: loop to cover all your sites
for site in $(honeylog sites --type sites \
| jq -r '.data.sites[].wireable'); do
honeylog --format csv breakdown "$site" --dimension bot_name \
--range yesterday > "bots-${site#site:}.csv"
done
Common options
Global options go before the command (honeylog --format csv breakdown …, not honeylog breakdown … --format csv):
| Option | What it does |
|---|---|
--format table|json|jsonl|csv |
Output format. Defaults to table in a terminal and json when the output is piped |
--token TOKEN |
An API token instead of the login. Prefer HONEYLOG_TOKEN: other users of the machine can see command-line flags in the process list |
--base-url URL |
The HoneyLog address. Without it, the CLI uses HONEYLOG_BASE_URL, then the address of the last honeylog login, then https://honeylog.io |
--timeout SECONDS |
Seconds to wait for each request. Defaults to 60 |
--retries N |
Retries after a rate limit or a network error. Defaults to 3 |
--no-retry |
Fail on the first rate limit or network error |
-v, --verbose |
Print each request to stderr, and where the token comes from. The token itself is never printed |
--version |
Print the CLI version |
These options set the period on every command that reads data:
| Option | What it does |
|---|---|
--range PERIOD |
A named period, resolved in the site's timezone each time the command runs, so a scheduled --range yesterday always means the day before. The report commands take yesterday, last_7_days, last_14_days, last_30_days, last_90_days, last_365_days, this_month, last_month, this_year and last_year. The raw-log commands take today, yesterday and last_7_days |
--start, --end |
An exact window instead of --range, as Unix timestamps or dates such as 2026-10-01 or 2026-10-01T10:00:00Z, in the site's timezone |
--compare-range PERIOD |
A comparison window relative to the main one: previous_period, previous_month or previous_year. Report commands only |
--compare-start, --compare-end |
An exact comparison window, for dates --compare-range cannot express |
--last DURATION |
The window up to now, such as 30m, 6h or 2d. Raw-log commands only |
Dates and defaults in the API documentation explains each period, and the default window when you give none.
These options filter and page:
| Option | What it does |
|---|---|
--where FILTER |
Filter on dimensions, as JSON, @file or - for standard input. The syntax is described under Filter expressions |
--having FILTER |
Filter on metric values, such as [[["views",">",100]]]. Report commands only |
--limit N, --offset N |
One page of rows |
--all |
Follow the pages and return every row, up to --max-rows (10,000 by default). The window is fixed by the first page, so the rows don't shift while the clock moves |
Every option also accepts the parameter's name in the API, with dashes: --where-expression, --having-expression and --order-direction work just like --where, --having and --order.
Commands
Besides login and logout, each command corresponds to one API endpoint and one MCP tool:
| Command | API endpoint | MCP tool | What it returns |
|---|---|---|---|
sites |
GET /api/v1/sites |
honeylog_list_sites |
The sites, custom views and networks the token can read, with their wireable |
fields WIREABLE |
GET /api/v1/{wireable}/fields |
honeylog_describe_fields |
The metrics and dimensions a site group accepts, or the raw-log columns |
timeseries WIREABLE |
GET /api/v1/{wireable}/stats/views |
honeylog_traffic_timeseries |
Metrics over time, with an optional comparison period |
breakdown WIREABLE |
GET /api/v1/{wireable}/stats/pages |
honeylog_traffic_breakdown |
Metrics per page, bot, country, referrer or any other dimension |
raw-logs WIREABLE |
GET /api/v1/{wireable}/raw-logs |
honeylog_raw_logs |
The individual requests |
raw-logs-timeseries WIREABLE |
GET /api/v1/{wireable}/raw-logs/timeseries |
honeylog_raw_logs_timeseries |
Requests per minute, hour or day |
WIREABLE is a single site group, written exactly as sites prints it: site:{uuid}, site:{uuid}@{customViewId} for a custom view, or network:{id}. honeylog COMMAND --help lists every option of a command.
sites
--search TEXT keeps the site groups whose domain or name contains the text, or the site with that uuid. --type sites or --type networks lists only one kind. --team-id ID keeps a single team when the token reads more than one. A long list comes in pages. The CLI says when there are more entries, and --all fetches all of them.
fields
--source raw_logs lists the raw-log columns instead of the report metrics and dimensions. Read this list before naming a field in --metrics, --dimension, --columns or a filter: the API rejects names outside it, and the list differs between site groups.
timeseries
--metrics takes a comma-separated list from fields. It defaults to views,sessions,viewers,avg_time_on_page,bounce_rate_sessions. --granularity takes minute, hour, day, week, month or year, and defaults to day. In table, CSV and JSON Lines output each time bucket is one row, with one column per metric under its display name ("Pageviews"), and a Compared … column for each metric when you compare.
breakdown
--dimension sets what the rows are grouped by, pathname by default. --dimensions adds further dimensions to break each row down by. --metrics works as in timeseries. --sort takes one of the requested metrics or the dimension, and --sort-order takes asc or desc. --limit returns 20 rows unless you ask for up to 1,000.
--with-totals adds the metrics over every row that matches the filters, not only the rows shown: use them for shares and overall figures instead of summing a page. They appear under the table, and in meta.totals in JSON.
raw-logs
--columns picks the columns, from fields WIREABLE --source raw_logs. By default you get datetime_second,bot_name,country_code,full_url,full_user_agent. --order asc returns the oldest requests first.
Raw logs read short windows: the last 24 hours unless you ask for another, and at most 8 days. A page holds 20 rows unless --limit asks for up to 1,000, and paging stops at an offset of 10,000. --all uses the largest page the API allows. When paging stops at that limit, the CLI says so: narrow the window or add a filter to reach the rest. For counts and top lists, breakdown is cheaper and complete.
raw-logs-timeseries
This command has a short alias, hits. It returns one row per bucket, with bucket (its start, in the site's timezone), timestamp and hits. Buckets without traffic are included with 0. --granularity takes 1m on windows up to 4 hours, 5m up to 24 hours, 15m up to 72 hours, and 1h or 1d on any window. Without it you get the finest size the window allows. Filters use the raw-log field names. In JSON output, meta.total_hits is the sum over the window.
Output formats
tableis meant for reading. Long cells are cut at 60 characters, so use another format for full URLs and user agents.jsonreturns the API's own answer,{"data": …, "meta": …}, unchanged. With--all,dataholds the rows of every page.jsonlprints one JSON object per row, andcsva header followed by one line per row. With--all, both print each page as it arrives.
Messages, warnings and errors go to stderr, so they never end up in a redirected file.
Rate limits and retries
Every command spends one call from your plan's API allowance per request: the same allowance as the REST API and the MCP server, not a separate one. With --all each page is one call, and raw-logs --all makes one more at the start, to ask for the largest page size.
When the per-minute limit is reached, the CLI waits as long as the API tells it to and tries again, up to 3 times and for at most 5 minutes in total. --retries changes the number of attempts and --no-retry turns retries off.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Unexpected error |
2 |
Wrong usage: not logged in and no token, an unknown option or a malformed WIREABLE or filter. Nothing was sent |
3 |
Token rejected, or no API access (HTTP 401 or 403). Also an expired login, or a honeylog login that did not complete |
4 |
No such site, custom view or network (HTTP 404) |
5 |
Invalid request (HTTP 422), for example an unknown metric. The errors are printed per field |
6 |
Still rate limited after the retries (HTTP 429) |
7 |
Server error (HTTP 5xx), including 504 for a query that took too long: narrow the window or add filters |
8 |
HoneyLog could not be reached |
Good to know
- Read-only: the CLI cannot change a setting, delete data or touch your billing, whatever the token carries.
- One site group per command: every request reads a single site, custom view or network. To cover several, loop over the output of
sites. - Dates are interpreted in the site's timezone, which every JSON answer repeats in
meta.timezone, so the figures match the dashboard. - The token stays private:
-vprints each request without it, the saved login is readable only by your user account, and the CLI refuses to send the token over plainhttpexcept tolocalhost.