Docs menuPull your stats with the REST API

Pull your stats with the REST API

Authenticate with an application password, fetch totals, top pages, sources, Search Console data and one post's stats with curl, and build a weekly report script.

On this page

Vireo’s REST API returns the same numbers as your dashboard, as JSON. It’s read-only and lives under /wp-json/vireo-analytics/v1. Every route is listed in the API reference.

1. Create an application password

The API signs in as a WordPress user, and only users who can open Vireo’s dashboard get an answer. See Who can see the numbers.

  1. In WordPress, go to Users, Profile.
  2. Under Application Passwords, enter a name such as “Reports script” and click “Add Application Password”.
  3. Copy the password. It’s only shown once.

WordPress only offers application passwords on sites served over HTTPS, or marked as a local development site.

2. Make your first request

Send the username and application password with HTTP Basic authentication. With curl, that’s -u:

curl -u "your-username:xxxx xxxx xxxx xxxx xxxx xxxx" \
  "https://example.com/wp-json/vireo-analytics/v1/stats/totals?start=2026-09-12&end=2026-10-11"
{"pageviews":15,"visitors":15,"days":30,"avg_daily":1,"avg_time_on_page_seconds":null,"timed_views":0,"bounce_rate_percent":null}

This test site recorded its views before Vireo Analytics 0.13.0, so it has no time on page or bounce rate yet, and those fields are null. On a site running 0.13.0 they hold numbers, such as "avg_time_on_page_seconds":74,"timed_views":512,"bounce_rate_percent":61.4. See the field list.

To keep the password out of your shell history, put it in an environment variable first:

export VIREO_AUTH="your-username:xxxx xxxx xxxx xxxx xxxx xxxx"
export VIREO_API="https://example.com/wp-json/vireo-analytics/v1"
curl -u "$VIREO_AUTH" "$VIREO_API/stats/totals?start=2026-09-12&end=2026-10-11"

The examples below use those two variables.

Dates and limits

  • start and end are dates as YYYY-MM-DD, in your site’s timezone. Both are required on the /stats/... routes.
  • Dates are kept within the last two years: an earlier date becomes the earliest allowed day, and a future date becomes today.
  • Routes that return a list take limit. Values run from 1 to 100; anything outside is brought into that range.
  • There’s no paging. A list returns the top limit rows, ranked.
  • Parameters a route doesn’t use are ignored.

Lists and rows

Routes that return a list answer in one of two shapes:

  • /stats/daily, /stats/top-pages, /stats/top-referrers, /stats/countries and /stats/events return a plain JSON array, as they always have. So does /stats/goals with Vireo Pro.
  • Every other list, such as /stats/channels, /stats/hourly, /stats/vitals and /stats/annotations, returns an object with the list under rows: {"rows": [...]}. An empty result is {"rows": []}.

Routes that return a single record, such as /stats/totals or /stats/post/{id}, return that object as it is. The API reference gives the shape of each route.

Top pages

curl -u "$VIREO_AUTH" "$VIREO_API/stats/top-pages?start=2026-09-12&end=2026-10-11&limit=3"
[
  {"post_id":4,"path":"/2026/10/11/how-to-pack-for-a-month/","title":"How to pack for a month","pageviews":6,"visitors":6,"avg_time_on_page_seconds":null},
  {"post_id":6,"path":"/2026/10/11/pricing/","title":"Pricing","pageviews":3,"visitors":3,"avg_time_on_page_seconds":null},
  {"post_id":5,"path":"/2026/10/11/a-week-in-porto-on-foot/","title":"A week in Porto on foot","pageviews":3,"visitors":3,"avg_time_on_page_seconds":null}
]

post_id is 0 for pages that aren’t a single post, such as the front page or an archive.

Where visitors came from

By channel:

curl -u "$VIREO_AUTH" "$VIREO_API/stats/channels?start=2026-09-12&end=2026-10-11"
{"rows":[
  {"source":"direct","label":"Direct","pageviews":3,"visitors":3,"share_pct":20},
  {"source":"search","label":"Search","pageviews":3,"visitors":3,"share_pct":20},
  {"source":"ai","label":"AI assistants","pageviews":3,"visitors":3,"share_pct":20},
  {"source":"social","label":"Social","pageviews":6,"visitors":6,"share_pct":40},
  {"source":"referral","label":"Referral","pageviews":0,"visitors":0,"share_pct":0}
]}

Unlike Top pages, this route wraps its list in rows. Lists and rows explains which routes do.

By site, and then one site in detail:

curl -u "$VIREO_AUTH" "$VIREO_API/stats/top-referrers?start=2026-09-12&end=2026-10-11&limit=10"
curl -u "$VIREO_AUTH" "$VIREO_API/stats/referrer?host=news.ycombinator.com&start=2026-09-12&end=2026-10-11"

Search Console

curl -u "$VIREO_AUTH" "$VIREO_API/stats/search-console?start=2026-09-12&end=2026-10-11&limit=10"

Add post_id=4, or path=/pricing/, for the searches behind one page. Google’s data runs two to three days behind.

If Search Console isn’t connected, you still get a 200, with connected set to false:

{"connected":false,"message":"Google Search Console is not connected. Connect it under Vireo Analytics, Settings, Search Console & API.","summary":null,"queries":[]}

One post’s stats

curl -u "$VIREO_AUTH" "$VIREO_API/stats/post/4?start=2026-09-12&end=2026-10-11"
{
  "post_id": 4,
  "title": "How to pack for a month",
  "url": "https://example.com/2026/10/11/how-to-pack-for-a-month/",
  "lifetime": {"pageviews":6,"visitors":6,"first_seen":"2026-10-11","best_day":{"date":"2026-10-11","pageviews":6}},
  "period": {"pageviews":6,"visitors":6},
  "daily": [{"date":"2026-09-12","pageviews":0,"visitors":0}, "…one row per day…"],
  "referrers": [{"host":"google.com","pageviews":3,"visitors":3},{"host":"chatgpt.com","pageviews":3,"visitors":3}]
}

A post that doesn’t exist, or that the user couldn’t read on the site (a draft, say), returns 404.

Errors

Status Code Meaning
400 rest_missing_callback_param A required parameter is missing, usually start or end.
400 rest_invalid_param A date isn’t YYYY-MM-DD.
400 vireo_rest_bad_range end is before start.
401 rest_forbidden No login, or WordPress didn’t accept the username and application password.
403 rest_forbidden The user is logged in but can’t see Vireo’s dashboard.
404 vireo_ability_not_found The post doesn’t exist or isn’t visible.

A Pro route on a site without an active Pro license returns 403, or 404 for routes Pro hasn’t registered.

Worked example: a weekly report

This script prints last week’s numbers as plain text. Run it from cron on Monday mornings and pipe the output into an email or a chat message. It needs curl and jq.

#!/usr/bin/env bash
# The last seven days' numbers from Vireo, as plain text.
set -euo pipefail

SITE="${VIREO_SITE:?Set VIREO_SITE, e.g. https://example.com}"
AUTH="${VIREO_USER:?Set VIREO_USER}:${VIREO_APP_PASSWORD:?Set VIREO_APP_PASSWORD}"
API="$SITE/wp-json/vireo-analytics/v1/stats"

# The last seven full days. GNU date uses -d, macOS date uses -v.
if date -v-1d >/dev/null 2>&1; then
  END=$(date -v-1d +%F); START=$(date -v-7d +%F)
else
  END=$(date -d yesterday +%F); START=$(date -d '7 days ago' +%F)
fi
RANGE="start=${VIREO_START:-$START}&end=${VIREO_END:-$END}"

get() { curl -fsS -u "$AUTH" "$API/$1"; }

totals=$(get "totals?$RANGE")
echo "Vireo report for ${VIREO_START:-$START} to ${VIREO_END:-$END}"
echo "Pageviews: $(jq .pageviews <<<"$totals")  Visitors: $(jq .visitors <<<"$totals")"
echo
echo "Top pages:"
get "top-pages?$RANGE&limit=5" | jq -r '.[] | "  \(.pageviews)  \(.title // .path)"'
echo
echo "How people arrived:"
get "channels?$RANGE" | jq -r '.rows[] | select(.pageviews > 0) | "  \(.label): \(.pageviews) (\(.share_pct)%)"'
echo
echo "What changed:"
get "changes?$RANGE" | jq -r '"  " + .summary'

Save it as vireo-weekly.sh, make it executable, and run it:

VIREO_SITE=https://example.com VIREO_USER=your-username VIREO_APP_PASSWORD="xxxx xxxx xxxx xxxx xxxx xxxx" ./vireo-weekly.sh

It reports on the last seven full days. Set VIREO_START and VIREO_END to report on other dates.

Vireo report for 2026-10-05 to 2026-10-11
Pageviews: 15  Visitors: 15

Top pages:
  6  How to pack for a month
  3  Pricing
  3  A week in Porto on foot
  3  /

How people arrived:
  Direct: 3 (20%)
  Search: 3 (20%)
  AI assistants: 3 (20%)
  Social: 6 (40%)

What changed:
  Collecting data, 15 views so far. Trends appear once there is a prior day to compare against.

The same requests work from n8n, Zapier or Make with an HTTP request step: Basic authentication with your username and application password, and the URLs above.

Vireo Pro

With an active Pro license, Pro’s routes sit under the same /stats/ path: real-time visitors, campaigns, engagement, funnels, revenue and more. They take the same start, end and limit, but start and end are optional there and default to the last 30 days. See the API reference.