Docs
Browse the docs

Analytics REST API

The Privatus Analytics REST API: add .json to any app URL and send a token. Conventions and a reference of every operation, generated from the code.

View as Markdown

Every page and action in Privatus Analytics is also an API endpoint (only form pages such as new and edit have no JSON form). There's no separate /api/v1: add .json to a URL and send an API token.

sh
curl https://privatusanalytics.com/sites/pa_7Q2K9XH3AB/stats/aggregate.json?period=7d \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
json
{
  "data": {
    "period": { "from": "2026-09-23T00:00:00Z", "to": "2026-09-30T00:00:00Z" },
    "metrics": {
      "visitors": 1840, "visits": 2310, "pageviews": 5122,
      "views_per_visit": 2.22, "bounce_rate": 41.3,
      "visit_duration": 48000, "revenue": 129900
    }
  }
}

The reference below lists every operation with its method, path, permission, input schema and response schema. It's generated from the running code, as is the OpenAPI 3.1 document, so neither can drift from what the server does. The same operations are MCP tools.

Conventions#

Base URL https://privatusanalytics.com
Format JSON. Add .json to the path (or send Accept: application/json)
Auth Authorization: Bearer pat_… (Authentication)
Ids Public ids with a prefix: ws_… workspaces, pa_… sites, goal_… goals, seg_… segments, wh_… webhooks
Reads GET with query parameters
Writes POST, PATCH, DELETE with a JSON body of flat fields
Success { "data": … }, plus "meta" for lists
Errors { "error": { "code", "message", "details" } } (Errors)
Lists page and per_page (max 100) (Pagination)
Times ISO 8601 in UTC. Date parameters (from, to) are days in the site's timezone
Money Minor units (cents) in the site currency, e.g. revenue: 129900 = 1,299.00
Durations Milliseconds

Common tasks#

Task Operation
List workspaces GET /workspaces.json
List sites GET /workspaces/{workspace_id}/sites.json
Headline metrics GET /sites/{site_id}/stats/aggregate.json
Chart data GET /sites/{site_id}/stats/timeseries.json
Top pages, sources, countries… GET /sites/{site_id}/breakdown/{dimension}.json
Everything on the Overview GET /sites/{site_id}/overview.json
Live visitors GET /sites/{site_id}/live.json
Add a note (e.g. a deploy) POST /sites/{site_id}/notes.json

See Stats endpoints and Examples.

In this section#

Authentication · Errors · Pagination · Rate limits · Filters · Stats endpoints · Examples · Webhooks · Changelog and deprecation

Every operation

Authentication

Send Authorization: Bearer pat_…. Create tokens under Workspace settings, API tokens. Authentication guide.

JSON everywhere

Add .json to any path. Responses are { "data": … } and errors are { "error": { "code", "message" } }. Errors.

MCP

POST JSON-RPC to https://privatusanalytics.com/mcp with the same token. Each operation below is a tool, except the ones marked HTTP only. MCP guide.

Download openapi.json · 527 operations

API tokens

POST /workspaces/:workspace_id/api_tokens.json Create an API token (the token is returned once) api_tokens_create

Creates an API token for the calling member in a workspace and returns the secret exactly once in the token field. Copy it immediately, it cannot be read again and a lost token must be revoked and recreated. A token can only narrow what its member may do. permissions defaults to all of the member's own permissions when omitted (an explicit empty list is refused, and asking for a permission you lack is an error). site_ids takes public site ids (pa_...) and restricts the token to those sites, and when empty the token works for every site its member can access (or keeps the site list of the token making this call). expires_at is optional ISO 8601 and must be in the future and at most 3 years away, omit it for a token that never expires. allowed_ips limits the token to those IPs or CIDR ranges. A token making this call needs tokens.manage. A workspace that requires two-factor authentication refuses tokens for members without a second factor of their own. Returns the same fields as api_tokens_list plus token.

Permission: member · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/api_tokens.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /workspaces/:workspace_id/api_tokens/:id.json Revoke an API token api_tokens_delete

Revokes an API token immediately and permanently. Requests that use it fail from then on, and a revoked token cannot be restored, create a new one instead. The token record stays in the list with status revoked (use api_tokens_list with include_revoked to see it), and revoking an already revoked token changes nothing. Take the id (a uuid) from api_tokens_list. You can revoke your own tokens, revoking someone else's needs tokens.manage and reach over every site the token covers. A token calling this needs tokens.manage. Returns the revoked token without its secret.

Permission: member · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/workspaces/:workspace_id/api_tokens/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/api_tokens.json List API tokens api_tokens_list

Lists the API tokens of a workspace, newest first and paginated. Without the tokens.manage permission you only see your own tokens, with it you see every member's. Revoked and expired tokens are hidden unless include_revoked is true. Each item has id (uuid, used by api_tokens_delete), name, token_prefix (the first characters, for recognizing a token), owner (the member's email), permissions (names such as analytics.read), site_ids (public site ids pa_..., an empty array means every site the owner can access), allowed_ips, oauth_client (the connected app's name when the token was issued through OAuth, otherwise null), status (active, expired or revoked), expires_at, last_used_at, revoked_at and created_at. The secret itself is never returned here, token is always null in a list. It is only shown once by api_tokens_create.

Permission: member

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/api_tokens.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Account

GET /account.json Get your profile and preferences account_get

Returns the signed-in member's own profile and preferences. Use it to read settings before changing them with account_update. This is about the person, not a workspace. Workspace plan and invoices are under billing_get. Fields are email, unconfirmed_email (set while an email change awaits confirmation, otherwise null), name (nullable), theme (system, light or dark), default_landing (sites or last_site), locale (language code for the signed-in app), timezone (IANA name, nullable), shortcuts_enabled, reduce_motion, product_emails, site_audit_emails, marketing_emails, newsletter (all booleans where true means subscribed), two_factor (boolean), passkeys (count of registered passkeys) and created_at (ISO 8601). A session opened only through a workspace identity provider sees just email and the display settings.

Permission: member

Example
curl https://privatusanalytics.com/account.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /account.json Update your profile and preferences account_update

Updates the signed-in member's own profile, display preferences and email subscriptions. Only the inputs you send change, everything else stays as it is. Changing email does not take effect at once. A confirmation email is sent to the new address (rate limited) and unconfirmed_email shows the pending address until it is confirmed. API tokens cannot change the email address and cannot turn marketing_emails back on after an unsubscribe, both need a signed-in browser. Turning marketing_emails off is allowed. Sessions opened by a workspace identity provider may only change the display settings (theme, default_landing, locale, timezone, shortcuts_enabled, reduce_motion). Returns the updated account in the same shape as account_get. This does not delete the account and cannot change workspace settings.

Permission: member

Example
curl -X PATCH https://privatusanalytics.com/account.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Ad creatives

POST /sites/:site_id/experiments/ads/:ad_placement_id/creatives.json Add a creative to an ad placement ad_creatives_create

Adds an image of the placement's shape (at least its size, twice the size looks sharpest) and the address a click goes to. Send the image as a multipart file, or image_url to have it downloaded. It is processed in the background, so poll ad_creatives_get until state is ready. A schedule, devices and paths are Pro and up. Requires content.write.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/experiments/ads/:ad_placement_id/creatives.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/experiments/ads/:ad_placement_id/creatives/:id.json Delete a creative and its image ad_creatives_delete

Permanently deletes a creative and its image. One that an experiment compares can't be deleted: switch it off instead. The MCP tool does not run it and responds with human_only and the page where a person deletes it. Requires content.write.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/experiments/ads/:ad_placement_id/creatives/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/experiments/ads/:ad_placement_id/creatives/:id.json Get one creative with its state ad_creatives_get

A creative is processing until its image is made, then ready (or failed, with the reason in error). Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/experiments/ads/:ad_placement_id/creatives/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/experiments/ads/:ad_placement_id/creatives/:id.json Change a creative ad_creatives_update

Changes the link, weight, schedule or targeting, switches it off (active false) or replaces the image. Only given inputs change. A new image replaces the old one when it is ready. Requires content.write.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/experiments/ads/:ad_placement_id/creatives/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Ad placements

POST /sites/:site_id/experiments/ads.json Create an ad placement ad_placements_create

Creates a slot of one size on the site. Send size (a common size such as medium_rectangle or leaderboard) or width and height in pixels. key is what the tag on the site names it by, and is made from the name when left out. Then add creatives with ad_creatives_create and put the tag from ad_placements_embed on the site. Requires content.write.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/experiments/ads.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/experiments/ads/:id.json Delete an ad placement with its creatives ad_placements_delete

Permanently deletes a placement with its creatives and their images, its ad experiments and their results, and its sponsor links. The tag on the site then draws nothing. The MCP tool does not run it and responds with human_only and the page where a person deletes it. Requires content.write.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/experiments/ads/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/experiments/ads/:id/embed.json Get the tag that shows a placement on the site ad_placements_embed

The loader script (once a page) and the slot for this placement, as html to paste. The slot keeps its size before the loader runs. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/experiments/ads/:id/embed.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/experiments/ads.json List a site's ad placements with their last 30 days ad_placements_list

Each placement with its key, size, how many creatives it has and stats (impressions, viewable impressions, clicks and ctr, the click rate per viewable impression) over the last 30 days. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/experiments/ads.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/experiments/ads/:id.json Get a placement with each creative's numbers ad_placements_get

The placement, its creatives with their stats over the period (7d, 30d or 90d) and the placement's numbers day by day. humans_only keeps what the bot score calls likely human (Pro and up). Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/experiments/ads/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/experiments/ads/:id.json Change an ad placement ad_placements_update

Changes the name, key, size or label. Only given inputs change. A new key needs the new tag on the site. Requires content.write.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/experiments/ads/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Affiliate program

GET /account/affiliate/commissions.json List your affiliate commissions affiliate_commissions_list

Commissions, refund and chargeback clawbacks and reversals, newest first. Amounts are in cents.

Permission: member

Example
curl https://privatusanalytics.com/account/affiliate/commissions.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /account/affiliate/signup-box.json Get your affiliate signup box code and results affiliate_signup_box_get

The embed code for your signup box (a script and a plain iframe) for the chosen theme, the attributes that prefill the form, the events the script fires on your page, and the box’s numbers: signups waiting to confirm, confirmed signups and paying customers. available is false until your affiliate application is approved.

Permission: member

Example
curl https://privatusanalytics.com/account/affiliate/signup-box.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /account/affiliate.json Get your affiliate status, referral link and stats affiliate_get

Your affiliate application status, referral link, signups and paying customers you referred, and balances per currency (on hold, payable, paid). status is none when you have not applied.

Permission: member

Example
curl https://privatusanalytics.com/account/affiliate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Alerts

POST /alerts/:id/acknowledge.json Acknowledge the latest incident alerts_acknowledge

Marks the latest unacknowledged incident of an alert as seen by the calling user. It does not resolve the incident or mute the alert, the alert resolves by itself when the condition clears. If the alert has no unacknowledged incident the call fails with a validation error. Returns the alert. Use alerts_history to see incidents with their acknowledged_at and acknowledged_by. Only needs analytics read access, so viewers can acknowledge.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/alerts/:id/acknowledge.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/alerts.json Create an alert alerts_create

Creates an alert rule that watches a site (or the whole workspace for kind usage) and notifies the chosen channels when it triggers. Path takes workspace_id. name and kind are required. kind decides what else is needed. threshold needs site_id, metric (visitors, visits, pageviews, bounce_rate or conversion), operator (gt, gte, lt or lte) and threshold, and conversion also needs goal_id. anomaly needs site_id and metric (visitors, visits or pageviews), compares the last window with the same window in each of the previous 4 weeks and uses sensitivity (low, medium or high). operator lt only alerts on drops, gt only on spikes and empty on both. goal needs site_id, goal_id and threshold (completions in the window). no_data needs site_id and threshold in hours without events (default 24). usage needs no site and threshold as percent of the monthly event allowance (default 80). uptime needs site_id and optionally uptime_check_id (default all checks of the site). ssl needs site_id and threshold in days before a certificate or domain expires (default 14). vitals needs site_id, metric (lcp, inp, cls, fcp or ttfb) and threshold in milliseconds (a score for cls). subscriptions needs site_id, metric (mrr_drop: MRR fell by more than threshold percent in the last 30 days, churn_rate or mrr_churn_rate: churn over the last 30 days is above threshold percent) and threshold (default 10), and skips while the site has no payment provider connected or nothing to measure. window_minutes (default 60, 5 to 10080) is how far back each evaluation looks. channel_ids come from notification_channels_list and sensitivity defaults to medium. Alerts are evaluated automatically every 5 minutes. bounce_rate and conversion thresholds are percents from 0 to 100. testimonials needs site_id and metric, which says what it watches: new (a new testimonial arrives), low_rating (a rating of 3 or lower arrives), approved (a testimonial is approved), allowance (the month's allowance of new testimonials is 80% used, used up, or holding a testimonial as waiting), sync_found (a sync finds new reviews), import_failed (an import fails, or a source fails three syncs in a row), removal_requested (someone asks for their testimonial to be removed) or request_bounced (a request email bounces). A testimonials alert is not evaluated every 5 minutes: it triggers at once each time the thing happens, records an incident that is already resolved, and its state stays ok. With metric allowance it watches the whole workspace and keeps no site. Returns the created alert with state ok.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/alerts.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /alerts/:id.json Delete an alert alerts_delete

Permanently deletes an alert rule and its whole incident history. This cannot be undone and notifications stop at once. To pause an alert without losing history use alerts_update with enabled false or alerts_mute. The id (alr_...) comes from alerts_list. Returns the deleted alert as it was. Requires content write permission.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/alerts/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /alerts/:id/history.json Incident history of an alert alerts_history

Lists the incidents of one alert, newest first and paginated. Each incident has triggered_at, resolved_at (null while still open), value (the measured number when it triggered, same unit as the alert threshold), message (a readable description), acknowledged_at and acknowledged_by (display name of the member, or null). Set open to true to see only unresolved incidents. Use alerts_acknowledge to mark the latest one as seen and alerts_get for the current state of the rule.

Permission: analytics.read

Example
curl https://privatusanalytics.com/alerts/:id/history.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /workspaces/:workspace_id/alerts.json List alerts alerts_list

Lists the alert rules of a workspace (path takes workspace_id), sorted by name and paginated. Returns a data array of alerts with meta pagination. Each alert has id (alr_...), site_id (pa_..., null for usage alerts which watch the whole workspace), name, kind, metric, operator, threshold, window_minutes, sensitivity, goal_id and goal_name, segment_name, filters_summary, channel_ids (nch_... ids) and channel_names, state (ok or triggered), enabled, muted_until, last_triggered_at, last_evaluated_at and created_at. Narrow it with site_id, state (ok or triggered) or kind. Use site_alerts_list to list the alerts of one site from a site path, alerts_get for a single alert and alerts_history for its incidents. A caller limited to some sites never sees usage alerts.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/alerts.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /alerts/:id/mute.json Mute (or unmute) an alert’s notifications alerts_mute

Silences the notifications of one alert for a number of minutes while it keeps evaluating and recording incidents in its history. minutes defaults to 60, the maximum is 43200 (30 days) and 0 unmutes it right away. The response is the alert with muted_until set to the end of the mute (null when unmuted). Use alerts_update with enabled false to stop evaluating completely and alerts_acknowledge to mark an incident as seen without muting.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/alerts/:id/mute.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /alerts/:id.json Get an alert alerts_get

Returns one alert rule by id (alr_..., from alerts_list or site_alerts_list). The response is the alert object with id, site_id (null for usage alerts), name, kind, metric, operator, threshold, window_minutes, sensitivity, goal_id and goal_name, filters, segment_id and segment_name, filters_summary (what the alert counts in words, null when it counts every visit), channel_ids and channel_names (in the same order), state (ok or triggered), enabled, muted_until, last_triggered_at, last_evaluated_at and created_at. It does not include incidents, use alerts_history for those. threshold is a number whose unit depends on the kind (see alerts_create). Requires analytics read access.

Permission: analytics.read

Example
curl https://privatusanalytics.com/alerts/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /alerts/:id.json Update an alert alerts_update

Changes an existing alert rule (id from alerts_list). Only the inputs you send change, everything else keeps its value. The same per kind rules as alerts_create apply to the result, so changing kind or metric may require the matching site_id, goal_id, operator or threshold. Send channel_ids to replace the whole list of notification channels, and enabled false to stop evaluating without deleting. To silence notifications for a while but keep recording incidents use alerts_mute instead. Returns the updated alert.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/alerts/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/alerts.json List alerts for a site site_alerts_list

Lists the alert rules of one site (path takes site_id), sorted by name and paginated. It returns the same alert objects and accepts the same state and kind filters as alerts_list, but only for this site, so usage alerts (which have no site) are not included. Choose this when you already have a site id and alerts_list when you want every alert in the workspace. Each alert has id (alr_...), site_id, name, kind, metric, operator, threshold, window_minutes, sensitivity, goal_id, channel_ids, state (ok or triggered), enabled, muted_until, last_triggered_at, last_evaluated_at and created_at.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/alerts.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Analytics

GET /sites/:site_id/clicks.json Click map: the most clicked elements of the site, or of one page with the share of its visits that clicked each clicks_list

Clicks per element from the clicks tracker module. An element is a short CSS selector (tag, id and up to two classes, at most four levels), or [data-privatus-click="name"] when the site named it. Pass path (starting with a slash, as stored) for one page: then elements are {selector, clicks, visits, visit_share} and page_visits is the number of visits that viewed the page, with visit_share the percent of them (0 to 100) that clicked the element. Without path, elements are {path, selector, clicks, visits} across the site. visits counts visits that clicked at least once. Returns period, path, page_visits and elements, most clicked first. No element text, coordinates or mouse movement is ever collected. Use pages_detail for the rest of a page.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/clicks.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/overview.json Site overview: metrics, chart series, top panels, live visitors and notes stats_overview

One call that returns everything the site overview page shows: use it to answer a broad question such as how a site is doing, instead of calling several tools. Returns period, metrics (visitors, visits, pageviews, views_per_visit, bounce_rate, visit_duration, revenue), interval, series (one entry per time bucket), panels, notes and live_visitors. With compare it adds comparison_period, comparison, change and comparison_series, as in stats_aggregate and stats_timeseries. Units match those tools: bounce_rate is a percent from 0 to 100, visit_duration is median engaged milliseconds, revenue is an integer in minor units (cents). panels is an object keyed by dimension name, each holding the top 10 rows with their share, in the same row shape as stats_breakdown for that dimension. By default it returns channel, referrer, page, entry_page, country, device, browser, os, utm_campaign, event and goal. Send panels to choose other dimensions (any dimension name except prop keys). notes lists annotations in the range as {id, date, end_date, title, url, color}. live_visitors counts visitors in the last 5 minutes. For more than 10 rows of a dimension use stats_breakdown, and for one number use stats_aggregate. Takes the shared period, compare, filters and segment inputs.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/overview.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/experiments/external.json Report for an A/B test run with another tool: conversion rate per variant from an event property, uplift vs control and significance (two-proportion z-test) experiments_report

Reports an A/B test using event data. Visits are assigned to the variant found in an event property (the first value seen in the visit, for example property variant with values control and b), and a conversion is a visit that completed a goal. Required inputs are property (lowercase key, up to 64 characters) and goal_id (an active goal, from goals_list). Returns period, property, goal {id, name}, control, confidence (0.95) and variants. Each variant has visits, conversions, conversion_rate (percent, 0 to 100), control (true for the baseline), uplift (percent change of the conversion rate against the control, null for the control or when the control rate is 0), z, p_value and significant (true when p_value is below 0.05, using a two-proportion z-test). The control is the variant you pass in control, else one named control, else the variant with most visits. At most 20 variants, busiest first. Make sure the site sends the variant as an event property first. Takes the shared period and filters inputs. For a plain goal conversion rate use goals_get. Needs the Business or Enterprise plan (402 plan_limit otherwise).

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/experiments/external.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/insights.json Notable changes vs the previous period, in plain English (biggest movers, goal conversion changes) insights_get

Plain-English notable changes for a site, compared with the previous period of the same length. Returns period, comparison_period, insights and benchmarks. The period covers whole days up to yesterday, chosen with period (7d, 14d, 28d or 30d, default 7d). Each insight has dimension (total, channel, page, country or goal), metric, value, previous, current, change and sentence. change is a percent for counts and percentage points for goal conversion rate, and sentence is a ready-to-read English line. It includes total visitors and pageviews, the two biggest movers per dimension (changes of 20 percent or more with at least 10 visits or views) and goals whose conversion rate moved by half a point or more. Most important first, limited by limit (default 8, max 20). Results are kept for the day. For exact figures use stats_aggregate and stats_breakdown, and use this to find what changed. benchmarks compares the site with all sites on Privatus Analytics over the last 28 days, whatever the period: enabled (false when the workspace switched benchmarks off), days, computed_at and rows. Each row has metric (bounce_rate, homepage_bounce_rate, visit_duration, views_per_visit, LCP, INP, CLS, FCP or TTFB), unit (percent, ms or number), better (lower or higher), value (this site), low, median and high (the 25th, 50th and 75th percentile of all sites), percentile (the share of sites this site is higher than, from 5 to 95), standing (better, typical or worse) and sites (how many sites the benchmark comes from). A metric appears only when the site has enough data and enough sites are behind the benchmark, so rows can be empty.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/insights.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/journeys.json Journeys: top 3-step paths after a page (or any entrance), or before a page or goal journeys_explore

Explores the most common three-step page paths inside visits. In forward mode (default) it shows the paths after a start page given in path, or after any entrance when path is blank. In reverse mode it shows the paths leading to an end page (path) or to a goal (goal_id, which switches to reverse automatically). reverse needs path or goal_id. path must start with a slash. Returns period, mode, path, goal, sampled, sample_rate (percent), total_visits and the path graph with nodes and links per step, limited to limit pages per step (2 to 12, default 6). Above 100,000 visits the data is sampled. Page filters are ignored because they would cut paths short. Paths are built per visit, since no visitor id is stored. Takes the shared period, filters and segment inputs. For one page's neighbors use pages_detail, and for step conversion use funnels_get.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/journeys.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/pages/detail.json Page detail: metrics, previous and next pages, sources, Web Vitals, events and click map for one path pages_detail

One page in depth. Pass path (it must start with a slash, for example /pricing), exactly as stored (without query string or host). Returns period, path, url, metrics, previous_pages, next_pages, sources, vitals, events, clicks, goals, notes and audited_page_id (the id of this page in the latest finished site audit for site_audit_pages_get, null when that audit did not crawl the path). metrics has visitors, visits, pageviews, entrances, exits, entry_rate, exit_rate and bounce_rate (percents, 0 to 100), time_on_page (median milliseconds) and scroll_depth (median percent), the last two null with no engagement data. previous_pages and next_pages are the pages seen before and after in the same visit, as {value, count, share}, with (entrance) and (exit) as special values. sources has the top channels and referrers of visits that entered on this page. vitals has p75 values per Web Vital with ratings. events are custom events fired on the page, clicks are the most clicked element selectors, goals are goals completed on it and notes are annotations that mention it. Use stats_breakdown with dimension page to rank pages and referrers_detail for a referring site.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/pages/detail.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/referrers.json Referrers: the sites that link to this site, with visitors, engagement, kind of traffic and number of linking pages referrers_list

Ranks the external sites that sent visitors, with what each one sent. Direct visits (no referrer) are left out. Returns period, summary, channels, page, limit, has_more and rows. summary has visitors, visits, pageviews, bounce_rate (percent), visit_duration (median engaged milliseconds), revenue (minor units, cents), sites (number of referring sites) and share (percent of all visits that came from referrers). channels shows the kinds of traffic as {value, visitors, visits}. Each row has value (the referrer host), visitors, visits, pageviews, bounce_rate, visit_duration, revenue, channel (its usual channel), linking_pages (how many different pages of that site link here) and share (percent of referred visitors). Sort with sort (visitors, visits, pageviews or revenue, default visitors), narrow with search (text contained in the host), and page through with page and limit (max 100) while has_more is true. Use referrers_detail for one site. Takes the shared period, compare, filters and segment inputs.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/referrers.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/referrers/detail.json Referrer detail: metrics and trend of one referring site, its linking pages, landing pages, countries, devices, campaigns and conversions referrers_detail

One referring site in depth. Pass referrer as the host without www and without a path (for example news.ycombinator.com), the same value as in referrers_list. Returns period, referrer, url, channel (its most common channel), metrics, share (percent of all visits), interval, series (the traffic over time), linking_pages, landing_pages, channels, countries, devices, campaigns, goals and, with compare, comparison, change and comparison_series. metrics and series use the same fields and units as stats_aggregate and stats_timeseries. linking_pages are the referrer's pages that sent visitors, as rows with path, url, visitors and share (query strings are never stored). landing_pages are where those visitors entered. channels, countries, devices and campaigns are top values with metrics, and goals lists goals they completed with conversions and conversion_rate. Use referrers_list to rank referring sites and stats_breakdown with dimension referrer for a simple list.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/referrers/detail.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/stats/aggregate.json Headline metrics for a period (with comparison) stats_aggregate

Headline numbers for one period, as one object. Returns period {from, to} and metrics {visitors, visits, pageviews, views_per_visit, bounce_rate, visit_duration, revenue}. With compare it also returns comparison_period, comparison (the same metrics for the other range) and change (percent change per metric, null when the earlier value was 0). Units: bounce_rate is a percent from 0 to 100 (10.9 means 10.9%). visit_duration is the median engaged time of a visit in milliseconds. revenue is an integer in minor units (cents) of the site currency. views_per_visit is a decimal. Visitors are counted per day because no persistent visitor id is stored, so someone who comes back on another day counts again. Use this for totals and period-over-period change. Use stats_timeseries for a chart over time, stats_breakdown for top values of a dimension, and stats_overview for all of it in one call. Narrow any of them with filters (see filters for the dimension names and operators).

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/stats/aggregate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/breakdown/:dimension.json Top values of a dimension (pages, sources, countries…) stats_breakdown

Top values of one dimension with their metrics, most important first, paginated. The dimension is the last part of the path and must be an exact name from its description (for example country, page, channel, utm_campaign, prop:plan). Returns period, dimension, label, page, limit, has_more and rows. Ask for the next page while has_more is true. Every row has value and share (percent of the period total). The other fields of a row depend on the dimension. Visit dimensions (channel, referrer, referrer_url, utm_*, ref, hostname, country, language, device, browser, browser_version, os, os_version, screen, entry_page, exit_page, interaction, bot_score) give visitors, visits, pageviews, views_per_visit, bounce_rate, visit_duration and revenue. page gives pageviews, visits, time_on_page (milliseconds) and scroll_depth (percent), with no visitors. event and prop:<key> give completions, visits, revenue and conversion_rate. goal gives goal_id, completions, conversions, conversion_rate and revenue. Units: bounce_rate and conversion_rate are percents (0 to 100), durations are milliseconds, revenue is in minor units (cents). A wrong dimension returns a 422 that lists the valid names. Use search to look for a value across all rows, and filters to narrow the data first (for example pages visited by one country). For one page or one referrer in depth use pages_detail or referrers_detail. The interaction and bot_score dimensions need the Business or Enterprise plan: as a breakdown they respond with 402 plan_limit on other plans, and a filter on one is left out.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/breakdown/:dimension.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/stats/timeseries.json Metrics per time bucket for charts stats_timeseries

Metrics per time bucket, for charts and trends. Returns period, interval (the bucket actually used) and series, a list of {date, visitors, visits, pageviews, views_per_visit, bounce_rate, visit_duration, revenue}, one entry per bucket with date as the ISO 8601 start of the bucket. With compare it also returns comparison_series for the other range, bucket by bucket. Units are the same as stats_aggregate: bounce_rate is a percent (0 to 100), visit_duration is median engaged milliseconds, revenue is in minor units (cents). interval defaults to auto, which picks minute, hour, day, week or month to suit the range. Set it to force a bucket size. Use stats_aggregate for a single total and stats_breakdown to split by a dimension such as country or page.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/stats/timeseries.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Assistant

POST /sites/:site_id/ask.json Ask a question in a new conversation (counts toward the monthly AI questions limit) assistant_ask

Starts a new Ask conversation on one site and answers its first question using that site's analytics. Use it for a plain-English question about a site. Use assistant_ask_followup to continue an existing conversation and the stats tools when you already know the exact query. The answer is produced by an AI model that runs read-only analytics queries on its own, so the call can take many seconds. It counts as one AI question against the workspace's monthly allowance (5 on Free, 20 on Pro, 100 on Business) and fails with a limit error when the allowance is used up. The question can be at most 2,000 characters. Conversations are private to the member who started them and are deleted 30 days after their last message. Returns the conversation (id, site_id, title taken from the question, created_at, updated_at, expires_at) with a messages array. Each message has role (user or assistant), content (the answer text), queries (the exact analytics queries the assistant ran), chart (nullable chart definition), goal_draft (nullable suggested goal), token counts and created_at. If the answer fails the conversation is removed and no question is counted. Requires analytics.read.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/ask.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/ask/:id.json Delete an Ask conversation and its messages assistant_conversations_delete

Permanently deletes one Ask conversation and all its messages right away. This cannot be undone. Get the conversation id from assistant_conversations_list. Only the member who started a conversation can see and delete it. Requires analytics.read and a site_id. Returns the deleted conversation summary (id, site_id, title, timestamps). It does not refund the AI questions already counted for the month.

Permission: analytics.read

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/ask/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/ask.json List your Ask conversations for a site assistant_conversations_list

Lists the signed-in member's Ask conversations on one site, most recently updated first. Conversations of other members are never shown. Use it to find a conversation id for assistant_conversations_get, assistant_ask_followup or assistant_conversations_delete. Each item has id, site_id, title (the first question, truncated to 80 characters), created_at, updated_at and expires_at but no messages. Paginated with page and per_page (maximum 100). Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/ask.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/ask/:id.json An Ask conversation: questions, answers, the queries behind them and charts assistant_conversations_get

Returns one Ask conversation with its full message history. Get the conversation id from assistant_conversations_list or from the result of assistant_ask. Fields are id, site_id, title, created_at, updated_at, expires_at (30 days after the last message, then the conversation is deleted) and messages. Each message has role (user or assistant), content, queries (the analytics queries the assistant ran), chart (nullable), goal_draft (nullable) and input_tokens and output_tokens (nullable integers). Only the member who started the conversation can read it. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/ask/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/ask/:ask_conversation_id/messages.json Ask a follow-up question in a conversation (counts toward the monthly AI questions limit). Returns the answer. assistant_ask_followup

Asks a follow-up question inside an existing Ask conversation, so the assistant keeps the earlier questions and answers as context. Use assistant_ask to start a new conversation instead. Get conversation_id from assistant_conversations_list. The call can take many seconds because the assistant runs read-only analytics queries. Each question counts against the workspace's monthly AI question allowance (5 on Free, 20 on Pro, 100 on Business) and fails with a limit error when it is used up. The question can be at most 2,000 characters. Returns only the new assistant message (not the whole conversation) with id, role, content (the answer), queries (the exact queries run), chart (nullable), goal_draft (nullable), input_tokens, output_tokens and created_at. Requires analytics.read and only works on your own conversations.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/ask/:ask_conversation_id/messages.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Audit log

GET /workspaces/:workspace_id/audit_log.json List audit log entries (add .csv to export) audit_logs_get

Returns the workspace audit log, a newest-first, paginated record of who changed what (settings, members, sites, goals, tokens and so on). It needs the workspace.manage permission and a plan with audit history. The Free plan has none and the call fails with a plan-limit error, Pro keeps 30 days, Business 365 days and Enterprise has no limit. Entries older than the retention window are never returned. Filter with actor (a member's email, exact match), item_type (the record kind such as Site, Goal or Membership), event (create, update or destroy), and from and to (YYYY-MM-DD, inclusive, in UTC). Each entry has id, created_at, event, item_type, item_id, target (a readable label such as Site: example.com), actor_email (null when the change was made by the system or by a deleted user), actor_name, token_name (the API token used, null for a browser session), changes (an object mapping each changed field to a [before, after] pair, with secrets removed), and ip (the address the member or API client used, null when unknown). A CSV export of the same log is available in the app only.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/audit_log.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Billing

POST /workspaces/:workspace_id/billing/badge.json Opt in or out of the badge bonus (free plan) billing_badge

Opts a Free workspace in or out of the Privatus Analytics badge bonus, which raises the monthly event allowance. Set enabled to true to opt in. A background check then looks for the badge on the workspace's sites and verifies it, which can take a moment, so read billing_get afterwards and look at badge.verified_at. Set enabled to false to opt out, which clears the check and verification dates and removes the bonus. Opting in on a Pro or Business workspace is refused because paid plans have no bonus. Returns the workspace billing object like billing_get. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/billing/badge.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/billing/cancel.json Cancel at the end of the billing period billing_cancel

Cancels the paid subscription at the end of the current paid period. Nothing is refunded and the plan stays active until current_period_ends_at, after which the workspace moves to the Free plan. Use billing_resume to undo it before then. Fails when the workspace has no subscription or a cancellation is already scheduled. The optional reason (one of the enum values) and comment are an exit survey. A comment, if given, is emailed to the Privatus Analytics team as a feedback message from the member and is not stored. Returns the billing object with cancel_at_period_end true. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/billing/cancel.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/billing/change_plan.json Change plan, tier or interval (prorated) billing_change_plan

Moves the existing subscription to another plan, event tier or billing interval. This charges money. The change takes effect immediately and Paddle prorates it, so a charge or credit is applied to the payment method on file right away. Run billing_preview first with the same inputs to see the exact amounts. Fails if the workspace has no subscription (use billing_checkout for that), if the target is the current plan, tier and interval, or if no price exists for the plan and events combination. The events value must be one of the tiers from plans_list. Returns the updated billing object. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/billing/change_plan.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/billing/checkout.json Start a Paddle checkout for a paid plan billing_checkout

Starts a Paddle checkout for a workspace that has no subscription yet and returns a hosted checkout URL. Nothing is charged until a person completes the checkout in a browser, so give the url to the person. Use billing_change_plan instead when the workspace already has a subscription. Inputs are plan (pro or business), events (an events a month tier from plans_list) and interval (month or year, default month). Prices include VAT or sales tax. An approved discount program is applied automatically and reported in discount_id. Returns transaction_id, url (nullable), price_id, plan, events, interval, amount (whole USD for the interval), client_token and environment (sandbox or production, used by the Paddle overlay), custom_data and discount_id (nullable). Fails if the workspace already has a subscription. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/billing/checkout.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/billing/discount.json Apply for a discount program (nonprofit, education, open source or agency) billing_discount

Applies for a discount program for the workspace. This submits an application for staff review and emails the Privatus Analytics team, it does not grant a discount by itself. Programs are nonprofit, education, open_source (50 percent off), open_source_free (free Pro up to 1M events a month) and agency (20 percent off every workspace you own). Only one open application (pending, approved or active) is allowed per workspace, and you can apply again after a rejection or revocation. license is kept only for the two open source programs. Returns the application with program, status, percent, comp_events (nullable), organization_name, website, proof_url, license, note, rejection_reason, created_at, reviewed_at, applied_at and revoked_at (nullable timestamps). Check progress later in the discount field of billing_get. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/billing/discount.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/billing/payment_method.json Get a link to update the payment method billing_payment_method

Returns a Paddle link where a person can update the subscription's payment method. Nothing is changed by the call itself. Give the url to the person to open in a browser. The result has kind (payment_method) and url. Fails when the workspace has no active subscription. For invoices, addresses and every other billing detail use billing_portal. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/billing/payment_method.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/billing/portal.json Get a Paddle customer portal link billing_portal

Returns a link to the Paddle customer portal, where a person can see invoices, payment methods and addresses. Nothing is changed by the call itself. Give the url to the person to open in a browser, The result has kind (portal) and url. Fails if the workspace has no Paddle customer yet (it never checked out) or when the customer is shared with other workspaces the caller cannot access, because the portal exposes all of the customer's data. Use billing_payment_method for just the card. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/billing/portal.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/billing/preview.json Preview a plan change with proration billing_preview

Shows what moving the subscription to another plan, tier or interval would cost right now, without changing anything. Always run it before billing_change_plan with the same plan, events and interval inputs. Fails if the workspace has no subscription or the target equals the current price. Returns plan, events, interval, price_id, amount (whole USD list price for the interval), currency, charge_minor (what would be charged now), credit_minor (credit for unused time) and result_minor (net amount) all in minor currency units, result_action (charge or credit, nullable), next_billed_at (nullable) and warnings (strings, for example last month's usage exceeding the new tier or plan limits such as sites or members that the new plan would break). Requires billing.manage.

Permission: billing.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/billing/preview.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /workspaces/:workspace_id/billing/resume.json Undo a scheduled cancellation or resume a pause billing_resume

Undoes a scheduled cancellation, or resumes a paused subscription, so the plan keeps renewing. Use it after billing_cancel and before current_period_ends_at. A paused subscription resumes immediately. Fails when there is no scheduled cancellation and the subscription is not paused. Returns the updated billing object. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/billing/resume.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/billing.json Get the plan, subscription and usage billing_get

Returns the workspace's plan and billing state. Use it before changing anything in billing, and to learn whether the subscription is active or cancelling. Fields include workspace_id, plan (free, pro, business or enterprise), plan_events (the sold events a month tier), allowance (events a month actually allowed, including any badge bonus), billing_interval (month or year, nullable), price (whole USD for the interval, nullable), subscription_status, current_period_ends_at, cancel_at_period_end (true when a cancellation is scheduled), canceled_at, auto_upgrade, discount (the latest discount application, nullable), discount_percent, billing_emails (array), tax_id, badge (opted_in, verified_at, checked_at and the bonus size) and usage (the same object billing_usage returns). Requires billing.manage. Invoices are in billing_invoices_list and a month-by-month usage view is billing_usage.

Permission: billing.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/billing.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:workspace_id/billing.json Update billing emails, tax id and auto-upgrade billing_update

Updates the workspace's billing settings. Only the inputs you send change. billing_emails is an array of up to 5 extra addresses for invoices and billing notices and replaces the whole list. tax_id is stored on the workspace and is not added to invoices, so it must also be entered in the checkout. auto_upgrade is false by default. When true, reaching the monthly event limit moves the workspace to the next tier and charges the difference instead of pausing collection. Turning it on while collection is already paused at the limit upgrades and charges immediately and sends a confirmation email, and if Paddle refuses the upgrade nothing is saved. Returns the billing object like billing_get. Requires billing.manage.

Permission: billing.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/billing.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/billing/usage.json Get this month's usage, forecast and per-site split billing_usage

Returns the workspace's event usage for one month. Use it to check how close the workspace is to its hard monthly limit. Inputs are month (any day in the month, defaults to the current month). Only pageviews and custom events count. Fields are month, events, bots (aggregate bot count, not billed), allowance, percent (0 to 100 and above, one decimal), forecast and forecast_percent (straight-line projection to month end, null for past months), days_left, status (ok, warning at 80 percent, over at 100 percent, paused or locked), sites (per site site_id, domain, name, events and share percent, largest first), history (previous months as month and events), access_locked_at, collection_paused (true when new events are being dropped), pause_reason (limit or staff, nullable), collection_paused_at and resumes_at (the 1st of next month when paused by the limit). Requires billing.manage.

Permission: billing.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/billing/usage.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /workspaces/:workspace_id/billing/invoices.json List invoices billing_invoices_list

Lists the workspace's billing transactions, newest first. Each item has id (use it with billing_invoices_get), invoice_number (nullable), status, amount_minor (integer in minor currency units, so 900 means 9.00), currency (ISO code such as USD) and billed_at (nullable ISO 8601). Paginated with page and per_page (maximum 100). It lists transactions only. To get the PDF call billing_invoices_get, and to update the card use billing_payment_method. Requires billing.manage.

Permission: billing.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/billing/invoices.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /workspaces/:workspace_id/billing/invoices/:id.json Get the PDF link for an invoice billing_invoices_get

Returns a link to the PDF invoice of one billed transaction. Get the invoice id from billing_invoices_list (the id field, a Paddle transaction id). The result has kind (invoice) and url, which is a link to the PDF from Paddle, so fetch or share it soon after the call. Fails if the id does not belong to this workspace or Paddle has no invoice for it. Requires billing.manage.

Permission: billing.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/billing/invoices/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Bot detection

GET /sites/:site_id/bot-detection.json Advanced Bot Detection report: hits on the site's honeypot pages, by page and by what gave the bot away bot_detection_report

Reports the hits on the site's honeypot pages. A honeypot page is a page reached only through a hidden link and set up as the Advanced Bot Detection page in the app shows. These hits are bots, so they are never pageviews or visits and do not appear in stats_overview or stats_breakdown. Choose the range with period (7d, 30d (default), 90d or 12mo). Hits are kept for 12 months. The result has period, from (the start of the range), has_data (false until the first hit), totals (hits, distinct pages, automated (hits where the tracker saw an automated browser) and no_interaction (hits with no pointer, key or touch input)), pages (up to 50 rows with path, hits and last_hit_at), reasons (hits per result of the standard bot filter: known_bot, automation, datacenter, empty_user_agent, referrer_spam, or undetected for a hit the filter would have counted as a person) and recent (the latest 50 hits with occurred_at, path, country, browser, device, bot_reason, automation and interacted). No IP address or User-Agent is kept with a hit, only keyed hashes of them that can't identify anyone, and those are never returned. Needs analytics.read and the Business or Enterprise plan (402 plan_limit otherwise).

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/bot-detection.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Campaigns

POST /sites/:site_id/campaigns/costs.json Set the spend for a campaign in a month campaign_costs_create

Records ad spend for one campaign in one month, so campaigns_list can show cost per acquisition and return on ad spend. If an entry for the same campaign and month already exists it is replaced, not duplicated, so this is also safe to call twice. campaign is the utm_campaign value as it appears in your tracked links, month is YYYY-MM (a full date such as 2026-09-15 is accepted and snaps to the first of the month), and amount is in major units as a number (1250.50, not minor units). currency is an optional 3-letter ISO code and defaults to the site currency. The response returns the stored entry with amount_minor in minor units. For many rows at once use campaign_costs_import. Needs content.write. Needs the Business or Enterprise plan (402 plan_limit otherwise).

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/campaigns/costs.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/campaigns/costs/:id.json Delete a campaign cost entry campaign_costs_delete

Permanently deletes one spend entry, found by its id from campaign_costs_list. This cannot be undone, and CPA and ROAS for that campaign and month stop including the spend right away. Other months and campaigns are not touched. Returns the deleted entry. Needs content.write. Needs the Business or Enterprise plan (402 plan_limit otherwise).

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/campaigns/costs/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/campaigns/costs/import.json Import campaign costs from CSV (campaign,month,amount[,currency]) campaign_costs_import

Bulk-imports ad spend from CSV, the quick way to load many campaigns and months. Send the CSV text in csv (best for tools and API calls) or upload a file in file (multipart only). The first row must be a header containing campaign, month and amount, with an optional currency column, for example campaign,month,amount,currency then spring_sale,2026-09,1250.50,USD. Month is YYYY-MM, amount is in major units, currency defaults to the site currency. Rows upsert by campaign and month, so re-importing the same file does not duplicate. Limits are 1 MB and 5,000 rows. A bad row does not stop the import. The response has imported (count saved), failed (count rejected) and errors (the first 50 messages, each naming its row, where row 2 is the first data row). A missing header column, an oversized file or malformed CSV fails the whole request. Needs content.write. Needs the Business or Enterprise plan (402 plan_limit otherwise).

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/campaigns/costs/import.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/campaigns/costs.json List campaign cost entries campaign_costs_list

Lists the ad spend entries recorded for a site, newest month first, then by campaign name. Each row is one campaign in one month with amount_minor (an integer in minor currency units, so 125050 means 1,250.50), currency (ISO 4217, uppercase), month as YYYY-MM, and the entry id (a UUID, use it for campaign_costs_update and campaign_costs_delete). Pass campaign to see one campaign only (case-insensitive match on the utm_campaign value). Paginated with page and per_page (max 100). Costs feed CPA and ROAS in campaigns_list, so this is where to check what spend a campaign has. Needs analytics.read. Needs the Business or Enterprise plan (402 plan_limit otherwise).

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/campaigns/costs.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/campaigns/costs/:id.json Change a campaign cost entry campaign_costs_update

Changes an existing spend entry, found by its id from campaign_costs_list. Only the inputs you send change, so send just amount to correct a number. amount is in major units (1250.50) and is stored as minor units, month is YYYY-MM, currency is a 3-letter ISO code. Changing campaign or month to a combination that another entry already has fails with a validation error instead of merging. To correct spend for a campaign and month without knowing the id, call campaign_costs_create, which replaces the existing entry. Needs content.write. Needs the Business or Enterprise plan (402 plan_limit otherwise).

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/campaigns/costs/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/campaigns/builder.json Build and validate a UTM-tagged URL campaigns_builder

Builds a UTM-tagged URL for a landing page and checks the values. Nothing is saved and no data is read, so it is safe to call repeatedly. Pass destination (a full https:// URL) plus utm_source, utm_medium, utm_campaign, utm_content and utm_term as needed, or pick a preset (newsletter, social, paid_search and so on) to fill utm_source and utm_medium when you leave them blank. Returns url (null when there is an error), valid, destination, preset, the cleaned utm_* values, issues (a list of {field, level, code, message}, where level error blocks the URL and warning only suggests a fix such as lowercasing, and code is lowercase, spaces, personal_data, destination_missing or destination_invalid) and presets (the available preset values). Values are cleaned to lowercase without spaces. Use campaigns_hygiene to find messy UTM values that already exist in your traffic and campaigns_list to report on campaigns that have run. To keep a tagged link with a short URL and click counts, use links_create instead.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/campaigns/builder.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/campaigns/hygiene.json UTM hygiene: near-duplicate values, style issues and unknown sources campaigns_hygiene

Finds messy UTM values in the traffic of a period so you can fix tagging. Returns period, duplicates, style_issues and unknown_sources. duplicates groups values of utm_source, utm_medium or utm_campaign that differ only by case, spaces or punctuation (Facebook, facebook and face-book), each as {field, suggestion, variants: [{value, visits}]} with the most visited spelling suggested. style_issues lists values that break the lowercase, no-spaces convention as {field, value, visits, suggestion} (up to 50). unknown_sources lists utm_source values that are not a recognized channel as {value, visits} (up to 50). Only the top 500 values per field are scanned. Counts are visits. Suggestions are advice only and nothing is changed. To merge variants into one channel, use channel_rules_create. Takes the shared period and filters inputs. Use campaigns_list for performance and campaigns_builder to create clean tagged URLs.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/campaigns/hygiene.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/campaigns.json Campaign rollup with conversions, revenue, cost, CPA and ROAS campaigns_list

Campaign performance report built from utm_campaign, with cost and return on ad spend. Returns period, currency (the site currency), goal_id and goal_name (the goal whose conversions are counted), campaigns, totals and unconverted_costs. Each campaign has visitors, visits, conversions, conversion_rate (percent from 0 to 100, conversions divided by visits), revenue and sources (rows per source and medium with the same metrics). When costs exist (from campaign_costs_list) a campaign also has cost, cpa (cost per conversion, null when there are no conversions) and roas (revenue divided by cost, a ratio such as 2.5, null when there is no cost). Money (revenue, cost, cpa) is an integer in minor units (cents) of the site currency. Costs are matched to campaign names case-insensitively and prorated by days of each month inside the period. A campaign with cost but no traffic is still listed. unconverted_costs lists spend with no exchange rate yet as {campaign, amount_minor, currency} in its own currency, left out of cost, cpa and roas. At most 100 campaigns, busiest first. Only visits with a utm_campaign are included. goal_id defaults to the first active goal, so pass it explicitly to measure a specific goal (ids come from goals_list). Use campaigns_hygiene to clean up names and stats_breakdown with dimension utm_campaign for a plain traffic split.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/campaigns.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Channel rules

POST /sites/:site_id/channel_rules.json Add a channel rule channel_rules_create

Adds a custom channel rule at the end of the list, so it runs after all existing rules and the built-in taxonomy only applies when no rule matches. channel is the name to assign (up to 32 characters, e.g. Partners). conditions is a non-empty list of objects with field (referrer_host, utm_source, utm_medium, utm_campaign, ref or path), operator (is, contains, starts_with or regex) and value. All conditions must match for the rule to apply, and matching is case-insensitive. A condition with a blank value is dropped, and a rule left with no conditions is rejected. A regex that does not compile is rejected. The rule applies to new visits within minutes and does not change history. Use channel_rules_move to reorder and channel_rules_preview to test it. Needs sites.manage.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/channel_rules.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/channel_rules/:id.json Delete a channel rule channel_rules_delete

Deletes a custom channel rule, found by its id from channel_rules_list. Visits it already classified keep their channel, and new visits fall through to the next matching rule or the built-in taxonomy. Remaining rules keep their order. Returns the deleted rule. Needs sites.manage.

Permission: sites.manage

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/channel_rules/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/channel_rules.json List channel rules in order channel_rules_list

Lists the custom channel rules of a site in evaluation order (position 1 first). Rules run top to bottom against each new visit and the first rule whose conditions all match decides the channel. A visit that matches no rule gets the built-in channel (direct, organic_search, paid_search, email and so on). Each rule has id (a UUID), channel (the name it assigns), position (1-based), conditions (a list of field, operator and value objects, all of which must match) and created_at. Rules only affect visits recorded after the rule exists and never rewrite old data. Use channel_rules_preview to test a sample visit. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/channel_rules.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/channel_rules/:id/move.json Move a channel rule up or down channel_rules_move

Moves a channel rule within the evaluation order, because the first matching rule wins and order decides which channel a visit gets. direction is up or down (one place), or top or bottom (to the ends). A rule already at the edge stays put without error. Positions are renumbered 1 to n and the updated rule is returned with its new position. Use channel_rules_list first to see the current order. Needs sites.manage.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/channel_rules/:id/move.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/channel_rules/preview.json Preview the channel for a sample referrer and UTM tags channel_rules_preview

Dry run that tells you which channel a sample visit would get with the site's current rules. Nothing is stored. Send any of referrer (a URL or bare hostname, www and m prefixes are ignored), utm_source, utm_medium, utm_campaign, ref, path (landing path such as /pricing) and paid_click (true if the URL carried a click id like gclid). The result has channel (the winning channel name), custom_rule (true when a custom rule decided it, false when the built-in taxonomy did) and referrer_host (the cleaned hostname used for matching). A referrer on the site's own domains counts as internal. Use this after channel_rules_create or channel_rules_move to confirm the effect. Needs analytics.read.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/channel_rules/preview.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /sites/:site_id/channel_rules/:id.json Change a channel rule channel_rules_update

Changes a custom channel rule, found by its id from channel_rules_list. Send channel to rename the channel it assigns and conditions to replace the whole conditions list (the list is replaced, not merged, so send every condition you want to keep). Omitted inputs are left alone. Condition fields, operators and regex validity are checked the same way as in channel_rules_create. The change applies to new visits only. To change the order use channel_rules_move. Needs sites.manage.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/channel_rules/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Collection

POST /embed/ads/e.json Record that a creative was shown, seen or clicked, called by the ad loader ad_serving_event

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/embed/ads/e.json \
  -H "Content-Type: application/json" -d '{…}'
GET /embed/ads/:site.json A site's ad slots and the creatives each one shows, called by the ad loader ad_serving_slots

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/embed/ads/:site.json
POST /api/overlay.json The clicks of one page for the click overlay, called by pa.overlay.js with its pass click_overlays_read

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/api/overlay.json \
  -H "Content-Type: application/json" -d '{…}'
POST /api/logs.json Send server access logs for AI crawler reporting (JSON {lines: [...]} or combined log text, with the site's crawler log key as Bearer token) crawler_logs_create

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/api/logs.json \
  -H "Content-Type: application/json" -d '{…}'
POST /api/event.json Collect a browser hit (used by /js/pa.js) ingest_event

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/api/event.json \
  -H "Content-Type: application/json" -d '{…}'
GET /api/experiment.json Whether an experiment sends this visitor to a variant page, called by the experiments head script ingest_experiment

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/api/experiment.json
GET /js/exp/:site.json The script the optional experiments head tag loads for a site ingest_experiment_script

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/js/exp/:site.json
POST /api/survey.json Record that a survey card was shown, dismissed or answered ingest_survey

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/api/survey.json \
  -H "Content-Type: application/json" -d '{…}'
GET /api/surveys.json What survey to show on a page, called by the tracker's ask module ingest_surveys

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/api/surveys.json

Crawlers

DELETE /sites/:site_id/crawlers/connection.json Disconnect Cloudflare and delete its stored API token and the crawler hits it imported crawlers_cloudflare_disconnect

Disconnects Cloudflare from a site. This deletes the stored API token and every crawler hit that was imported from Cloudflare, and it cannot be undone. Hits that came from shipped server logs stay. The token still exists in your Cloudflare account until you delete it there. Fails if the site has no connection. Returns the removed connection. Needs sites.manage.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/crawlers/connection.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/crawlers/sync.json Import crawler hits from Cloudflare now crawlers_cloudflare_sync

Queues an immediate Cloudflare import for a site that is already connected, instead of waiting for the regular schedule. It returns right away (HTTP 202) with the connection, and the import runs in the background. Poll crawlers_report and watch cloudflare.last_synced_at to see when it finished. Fails if the site has no Cloudflare connection. Needs sites.manage.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/crawlers/sync.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/crawlers.json AI crawlers report: hits by crawler over time, top paths per crawler, by category crawlers_report

Reports which AI, search and SEO crawlers fetch the site, how often and which paths, from shipped server logs and the Cloudflare import. This is crawler traffic, not visitor traffic, so it does not appear in stats_breakdown. category is ai (default), search, seo, other or all, and crawler narrows to one named bot such as GPTBot or ClaudeBot. Choose the range with period (default 30d) or custom from and to. The result has period (from and to as dates), category, crawler, totals (hits, distinct paths, distinct crawlers), categories (hits per category, ignoring the category filter), crawlers (up to 50 rows with crawler, category, hits, paths), series (labels per day and daily hits for the top 8 crawlers), top_paths (the 10 most fetched paths for each of the top 8 crawlers), has_data (false until any hits exist) and cloudflare (the connection with zone, status and last_synced_at, or null). Results are cached for up to 30 minutes except right after a Cloudflare change. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/crawlers.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Dashboards

GET /workspaces/:workspace_id/dashboards/:id/cards/:index.json Data for one card of a custom dashboard dashboards_card

Returns the live data of one card of a custom dashboard. Dashboards are lists of cards (see dashboards_get for the card list) and each card loads separately, so call this once per card with its position in index, counted from 0. Returns index, type, title, site_id, site_name, params, status and data. status is ok, not_available (the card has nothing to show, for example a funnel card on a site with no funnels or an uptime card with no checks) or no_access (the card belongs to a site your token or role cannot open, and then title, params and data are empty). The shape of data depends on type. metric gives {metric, value, previous, change, period} compared with the previous period. chart gives {metric, interval, period, series: [{date, value}]}. breakdown gives {dimension, label, rows}. goal gives {rows} of goal completions. live gives {visitors}. funnel gives {funnel_id, name, overall_conversion_rate, steps}. uptime gives {checks: [{name, status, uptime}]}. vitals gives {scorecard}. subscriptions gives {currency, mrr_minor, previous_mrr_minor (30 days ago), change (percent, null without MRR 30 days ago), active_subscriptions, subscription_churn_rate (last 30 days)} and is not_available while the site has no payment provider connected. Card params default to period 30d, metric visitors, dimension page and 5 rows (limit is capped at 10). An index outside the list returns 404. Use dashboards_get first to see how many cards there are.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/dashboards/:id/cards/:index.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /workspaces/:workspace_id/dashboards.json Create a custom dashboard dashboards_create

Creates a custom dashboard in a workspace. A custom dashboard is a named, ordered list of up to 40 cards, and the cards can come from different sites of the workspace. Pass name (up to 100 characters) and cards, a list of objects with type (metric, chart, breakdown, goal, live, funnel, uptime, vitals or subscriptions), site_id (pa_..., from sites_list) and optionally title (up to 80 characters) and params. Params used per type are metric (period, metric), chart (period, metric, interval), breakdown (period, dimension, limit), goal (period, limit), funnel (period, funnel_id), uptime (limit) and vitals (period). live and subscriptions take none. Other params are dropped. The period must be a preset such as 30d (not custom) and metric one of visitors, visits, pageviews, views_per_visit, bounce_rate or visit_duration. Every card must point to a site your token can access, and a token limited to some sites must include at least one card. Returns the dashboard with id (dash_...), name, cards and timestamps. Read it with dashboards_get and load each card's numbers with dashboards_card. Needs the content.write permission. The plan sets how many dashboards a workspace has: 1 on Free, 3 on Pro, unlimited on Business. One over the count is refused with a validation error.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/dashboards.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /workspaces/:workspace_id/dashboards/:id.json Delete a custom dashboard dashboards_delete

Permanently deletes a custom dashboard. Only the dashboard layout is removed, no analytics data is touched, and there is no undo. You need access to every site that has a card on the dashboard, and the dashboard needs at least one card of a site you can access. Returns the deleted dashboard. Use dashboards_update to change a dashboard instead. Ids (dash_...) come from dashboards_list. Needs the content.write permission.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/workspaces/:workspace_id/dashboards/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/dashboards.json List custom dashboards dashboards_list

Lists the custom dashboards of a workspace, ordered by name. Each item has id (dash_...), name, cards and timestamps, the same shape as dashboards_get. A token limited to some sites only sees dashboards that include a card of one of its sites, and cards of other sites are blanked to {type, status: no_access}. Paginated with page and per_page (max 100). Use dashboards_card to load the data of a card and dashboards_create to add a dashboard. For a single site, stats_overview gives the same kind of numbers without a dashboard.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/dashboards.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /workspaces/:workspace_id/dashboards/:id.json Get a custom dashboard and its cards dashboards_get

Returns one custom dashboard: id (dash_...), name, cards and created_at and updated_at. Each card is {type, site_id, title, params} (type is metric, chart, breakdown, goal, live, funnel, uptime, vitals or subscriptions). A card on a site you cannot access is returned as {type, status: no_access} with no other fields. This returns the layout only, not the numbers. Load the numbers of a card with dashboards_card, using the card's position counted from 0. Ids come from dashboards_list. A dashboard over the plan's count (1 on Free, 3 on Pro) is kept and locked: it responds with 402 plan_limit until the workspace upgrades or deletes another one.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/dashboards/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:workspace_id/dashboards/:id.json Rename a custom dashboard or replace its cards dashboards_update

Renames a custom dashboard and/or replaces its whole list of cards. Only the fields you send change, and name is left alone when empty. Sending cards replaces every card, so send the full list in the order you want, taken from dashboards_get and edited. Card format and params per type are the same as in dashboards_create (max 40 cards). A card shown as {type, status: no_access} belongs to a site you cannot open. Send it back unchanged to keep it, it cannot be edited and the last card of such a site cannot be removed. A token limited to some sites cannot leave the dashboard empty. Returns the updated dashboard. Needs the content.write permission.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/dashboards/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Documentation

GET /docs/api.json List every API operation and MCP tool docs_api

Returns the full catalog of every API operation. Most are MCP tools too, and mcp is false for the ones that are HTTP only or that a person does in the app (deleting data, billing, access and security). Use it to discover which tool or endpoint exists and exactly what it takes. Each item has tool_name, summary, description (nullable), group, verb (HTTP method), path (template with placeholders), permission (the permission a member or token needs, nullable for public tools), mcp (true when it runs as an MCP tool), input_schema (JSON Schema of the inputs) and response_schema (JSON Schema of the response). The response is large. For one tool you already know, prefer the docs pages (docs_search). docs_openapi returns the same information as an OpenAPI document. Public, no authentication needed.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/docs/api.json
GET /docs.json List documentation pages docs_list

Lists every documentation page with title, description, path (such as /docs/api/authentication), section and no body. Use it to browse what exists. To find a page by topic use docs_search, and to read one use docs_get with its section and page. Paginated with page and per_page (maximum 100). Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/docs.json
GET /openapi.json OpenAPI 3.1 document for the whole API docs_openapi

Returns the OpenAPI 3 document describing the whole public API as raw JSON (not wrapped in a data field). Use it when you need a machine-readable spec for code generation. For a browsable list of operations with their input and response schemas, docs_api is easier to read. It describes the same operations that are exposed as MCP tools. Public, no authentication needed.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/openapi.json
GET /docs/:section/:page.json Read a documentation page (Markdown and HTML) docs_get

section and page come from the page path /docs/<section>/<page>. Use page "index" for a section overview.

Permission: public

Example
curl https://privatusanalytics.com/docs/:section/:page.json

Domain Rating

GET /sites/:site_id/domain-rating.json Domain Rating report: the latest rating, inbound and outbound links, the change since the release before and the history by month domain_rating_report

Returns the site's Domain Rating, a 0 to 100 estimate of how strong the links pointing at its domain are, plus inbound and outbound link counts and their history. This is Privatus Analytics' own score, not a Google metric. Ratings are updated once a month per release, so changes show monthly. months (1 to 120, default 12) sets how many monthly points of history to include. The result has domain, checked_at (when the site was last checked, null if never), release (the month of the latest data as YYYY-MM), rating, inbound_links, outbound_links (each null if not yet known), changes (the same three metrics as the difference against the previous release, null when either is unknown) and history (one row per month with month, rating, inbound_links, outbound_links, oldest first). A new site can be unrated for a few minutes. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/domain-rating.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Email reports

DELETE /recipients/:id.json Remove a recipient from an email report email_report_recipients_delete

Removes one address from an email report (id of the recipient, from email_report_recipients_list). The address stops receiving reports and its row is deleted. If the address had unsubscribed on its own the opt-out stays on record, so removing it does not allow anyone to invite it again. To change many addresses at once send the full list through email_reports_update instead. Returns the removed recipient.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/recipients/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /reports/:report_id/recipients.json List the recipients of an email report with their status email_report_recipients_list

Statuses: active (gets reports), pending (invited, not yet accepted), declined or unsubscribed. Add or remove addresses through the report's recipients list.

Permission: analytics.read

Example
curl https://privatusanalytics.com/reports/:report_id/recipients.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /recipients/:id/resend.json Resend a pending report invite email_report_recipients_resend

Sends the invite again with new links. Allowed once every 7 days per address per report.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/recipients/:id/resend.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/reports.json Create an email report email_reports_create

Creates a scheduled analytics email report covering one or more sites (path takes workspace_id). Required are name and site_ids (pa_... ids). frequency is daily, weekly (default, sent on weekday) or monthly (the 1st of the month), hour is the hour of day 0 to 23 in the workspace timezone (default 8) and weekday is 0 for Sunday to 6 for Saturday (default 1, only used by weekly reports). blocks picks the content from kpis, chart, pages, sources, goals, campaigns, vitals, uptime, testimonials and subscriptions (default kpis, chart, pages, sources, goals, testimonials, subscriptions). subscriptions shows MRR and what moved it in the period, for a site with a payment provider connected. recipients is a list of email addresses (up to 50). Members of the workspace are active at once, any other address is pending and gets an invitation email, and receives reports only after accepting it. Addresses that opted out are refused. If recipients is omitted the calling user is added. An enabled report needs at least one recipient. The Free plan allows 3 reports, paid plans are unlimited. Returns the report with next_run_at. Use email_reports_send_test to preview it.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/reports.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /reports/:id/deliveries.json Delivery log of an email report email_reports_deliveries

Lists the send log of one email report, newest first and paginated. Each entry has status (sent for a scheduled send, test, failed or skipped), recipients_count, error (the reason for a failure, otherwise null) and sent_at. Filter with status. Use it to confirm a scheduled report went out or to see why it failed. Use email_reports_send_test to trigger a test email.

Permission: analytics.read

Example
curl https://privatusanalytics.com/reports/:id/deliveries.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
DELETE /reports/:id.json Delete an email report email_reports_delete

Permanently deletes an email report together with its recipients and delivery log. This cannot be undone and no further emails are sent. To stop sending but keep the report use email_reports_update with enabled false. The id (rep_...) comes from email_reports_list. Returns the deleted report.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/reports/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/reports.json List email reports email_reports_list

Lists the scheduled email reports of a workspace (path takes workspace_id), sorted by name and paginated. Each report has id (rep_...), name, site_ids, frequency, hour, weekday, recipients (every address on the report), recipient_details (id, email, status, invited_at, expires_at, confirmed_at for each), blocks, attach_pdf, intro, enabled, last_sent_at, next_run_at and created_at. Filter by frequency (daily, weekly or monthly). A caller limited to some sites only sees reports whose sites are all theirs. Only recipients with status active actually receive reports. Use email_reports_deliveries for the send log.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/reports.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /reports/:id/send_test.json Send an email report now as a test email_reports_send_test

Sends the report to one address right now as a test, without changing its schedule or last_sent_at. Sends a real email, so only use it when the user wants that. recipient defaults to the calling user and may only be the caller, another member of the workspace or an active recipient of the report. For any other address the test goes to the caller instead. Test sends are rate limited per workspace and per outside address, and a refused send returns a rate limit error. Returns the delivery with status (test, or failed with the reason in error), recipients_count and sent_at. Use email_reports_deliveries for the full log.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/reports/:id/send_test.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /reports/:id.json Get an email report email_reports_get

Returns one email report by id (rep_..., from email_reports_list). The response has name, site_ids, frequency, hour, weekday, recipients, recipient_details with each address and its opt-in status, blocks, attach_pdf, intro, enabled, last_sent_at and next_run_at (UTC timestamps). recipients lists every address including pending ones, only addresses with status active in recipient_details receive reports. Use email_reports_deliveries for what was sent.

Permission: analytics.read

Example
curl https://privatusanalytics.com/reports/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /reports/:id.json Update an email report email_reports_update

Changes an email report (id from email_reports_list). Only the inputs you send change. Sending recipients replaces the whole list. Addresses you drop are removed, new ones follow the same opt-in rules as email_reports_create (members are active at once, others get an invitation email) and addresses already on the report keep their status. Changing frequency, hour, weekday or enabled recalculates next_run_at. Set enabled false to pause. Use email_report_recipients_list to see opt-in status and email_report_recipients_resend or email_report_recipients_delete to manage single addresses.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/reports/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /reports/invites/:token/accept.json Accept a report invite report_invites_accept

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/reports/invites/:token/accept.json \
  -H "Content-Type: application/json" -d '{…}'
POST /reports/invites/:token/decline.json Decline a report invite report_invites_decline

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/reports/invites/:token/decline.json \
  -H "Content-Type: application/json" -d '{…}'
POST /reports/resubscribe/:token.json Receive reports again after opting out report_resubscribes_confirm

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/reports/resubscribe/:token.json \
  -H "Content-Type: application/json" -d '{…}'
POST /reports/unsubscribe/:token/resubscribe.json Email an address a link to receive reports again report_resubscribes_create

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/reports/unsubscribe/:token/resubscribe.json \
  -H "Content-Type: application/json" -d '{…}'
POST /reports/unsubscribe/:token.json Unsubscribe an address from an email report report_unsubscribes_create

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/reports/unsubscribe/:token.json \
  -H "Content-Type: application/json" -d '{…}'

Email settings

POST /email/preferences/:token/unsubscribe.json Stop every email to an address that it can turn off, with the token from its email settings link email_preferences_unsubscribe_all

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/email/preferences/:token/unsubscribe.json \
  -H "Content-Type: application/json" -d '{…}'
POST /email/preferences/:token.json Turn emails to an address off or on with the token from its email settings link email_preferences_update

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/email/preferences/:token.json \
  -H "Content-Type: application/json" -d '{…}'

Events

GET /sites/:site_id/events/properties.json List event property keys seen in the last 30 days event_properties_list

Lists the property keys that custom events carried in the last 30 days, with display settings. Returns a list of {key, events, display_name, hidden, export_safe}. events is how many custom events carried the key (0 for a key that has settings but was not seen recently), display_name is the rename (null when none), hidden says the key is hidden from reports, and export_safe says it is marked as containing no personal data. At most 200 keys are returned, most used first. Pass event to see only keys seen on one raw event name. Property values are never returned here, use stats_breakdown with dimension prop:<key> or events_get for values. Change settings with event_properties_update.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/events/properties.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/events/properties.json Rename or hide an event property key event_properties_update

Renames, hides or marks as export-safe one event property key, for the whole site. Only the inputs you send change. key must be the key as stored (lowercase letters, digits and underscores, up to 64 characters, for example plan). display_name is a label of up to 64 characters (send it empty to remove the rename). hidden removes the key from reports and the event detail pivot while the data is kept. export_safe declares that the key never holds personal data, which includes its values in raw exports and warehouse syncs, so set it only when that is true. Renames and hiding apply when reports are read and rewrite no stored data. Returns {key, display_name, hidden, export_safe}. Needs the sites.manage permission. List the keys with event_properties_list.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/events/properties.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/events.json Delete an event and its history (runs in the background, confirm by typing the name) events_delete

Permanently deletes a custom event and its whole history. This cannot be undone. The raw event name must be sent twice, in name and again in confirm, as a safety check. Returns {name, status: queued} with HTTP 202 because the rows are removed in the background in batches, so numbers keep changing until it finishes. The event is also hidden right away, and the deletion is recorded in the audit log. Pageviews are not affected. If you only want to stop showing an event, use events_update with hidden true, which keeps the data. Needs the sites.manage permission. Names come from events_list (use the raw_names field if the event was renamed or merged).

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/events.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/events.json List events with completions, visits, conversion rate and first/last seen events_list

The events explorer: every custom event in the period. Returns period, events and hidden. Each event has name (the display name after renames and merges), raw_names (the names the tracker sent), completions, visits, conversion_rate (percent of visits, 0 to 100), revenue (minor units, cents), first_seen and last_seen (ISO 8601 timestamps within the period) and currency (the revenue currency, null when unset). hidden lists events you hid as {name, display_name}. Use search to keep events whose name contains some text. Pageviews are not events here. Use events_get for one event in depth and stats_breakdown with dimension event for a ranked, paginated list. Takes the shared period, compare, filters and segment inputs.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/events.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/events/merge.json Merge several events into one display name events_merge

Merges several raw event names into one display name, for example signup_click and signup_button into Signup. Reports then show a single event with the combined counts. It works when reports are read and the stored events are not changed or deleted. Pass names (the raw names, at least one) and into (the display name). Returns one settings object per merged name, each {name, display_name, hidden, currency, updated_at}. To undo, call events_update for a name with an empty display_name. Needs the sites.manage permission. Names come from events_list (raw_names).

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/events/merge.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/events/detail.json One event: chart, property pivot, number property aggregates, pages and sources events_get

Detail of one custom event. Pass name exactly as shown in events_list (the display name after renames and merges). Returns period, name, raw_names (the stored names behind it), interval, totals, setting, series, property_keys, pivot, numeric, pages, channels and referrers. totals has completions, visits (visits that fired it), revenue (minor units, cents) and conversion_rate (percent of all visits in the period). series is completions per time bucket. setting has display_name, hidden and currency. property_keys lists the property keys seen. Send properties (up to 3 keys) to get pivot, rows of {values, completions, visits} for each value combination, top 100. numeric gives sum, average and other figures for properties that hold numbers. pages, channels and referrers are the top 10 places the event fired, as {value, completions, visits}. Use events_list for all events, stats_breakdown with dimension prop:<key> for one property across the site, and events_update to rename it.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/events/detail.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/events/settings.json Rename, hide or unhide an event, or set its currency events_update

Changes how one custom event is shown: rename it with display_name, hide or unhide it with hidden, or set the revenue currency. name is the raw event name as the tracker sent it (see raw_names in events_list). Only the inputs you send change. An empty display_name removes the rename. hidden removes the event from lists and reports while its data is kept. currency is a three-letter ISO 4217 code such as USD, stored in uppercase. This works when reports are read and no stored events are rewritten. Returns {name, display_name, hidden, currency, updated_at}. To combine several events use events_merge, and to erase data use events_delete. Needs the sites.manage permission.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/events/settings.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Experiments

POST /sites/:site_id/experiments.json Create an experiment as a draft experiments_create

Creates an A/B or split URL test for a site. It is saved as a draft and sends no visitor anywhere until a person starts it in the app. Required are name, path (the original page, such as /pricing), goal_id (from goals_list) and variants: a list of { name, path, weight } where the first row is the original page (its path is ignored) and each other row is a page of the same site. Leave every weight out for an even split, or send weights that add up to 100. The Free plan allows one experiment at a time with the original and one variant. Pro and up add more variants, automatic allocation, targeting, a * in the path, secondary goals, a scheduled start and exclusive groups. Requires content.write. Returns the experiment.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/experiments.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/experiments/:id.json Delete an experiment with its results experiments_delete

Permanently deletes an experiment with its variants and results. This cannot be undone. Use experiments_pause to stop it and keep the results. The MCP tool does not run it and responds with human_only and the page where a person deletes it. Requires content.write.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/experiments/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/experiments/:id/duplicate.json Copy an experiment as a new draft experiments_duplicate

Creates a draft with the same pages, shares, goal and rules as an existing experiment and none of its results. Use it to change a test that has already started. Counts toward the plan limit like a new experiment. Requires content.write. Returns the new experiment.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/experiments/:id/duplicate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/experiments/:id/end.json End an experiment for good experiments_end

Ends a running or paused experiment. It cannot be started again, its results are kept and it no longer counts toward the plan limit. The MCP tool does not run it and responds with human_only and the page where a person ends it. The API runs it. Requires content.write.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/experiments/:id/end.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/experiments.json List a site's experiments with their visits and conversions experiments_list

Lists the A/B and split URL tests of a site, newest first. Filter with status (draft, scheduled, running, paused, ended or all, default all). Paginated with page and per_page (default 25, up to 100). Each experiment has id (exp_...), name, status, path (the original page), goal_id and goal_name, variants (id, name, path, weight, control), the rules for ending it, locked (true when the plan no longer covers it) and stats (visits, conversions and conversion_rate across all its variants, null until it has started). Use experiments_get for the results per variant. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/experiments.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/experiments/:id/pause.json Pause an experiment and keep its results experiments_pause

Pauses a running or scheduled experiment. Every visitor gets the original page again at once and the results so far are kept. experiments_start resumes it. Requires content.write. Returns the experiment.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/experiments/:id/pause.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/experiments/:id.json Get one experiment with its results experiments_get

Returns one experiment by id (exp_..., from experiments_list) with its results: per variant the visits, conversions and conversion rate on the goal, and against the original the uplift, the likely range of the difference, the p-value, whether it is significant at the chosen confidence and the chance that it beats the original. results.progress says how far it is from being able to call a winner, and results.mismatch.found true means the visits are not shared out as the weights say, so the result should not be trusted. split breaks the results down by device, channel or country (Pro and up) and humans_only keeps visits the bot score calls likely human (Pro and up). A visit is counted for a variant when it saw that variant, and a conversion counts only after that. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/experiments/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/experiments/:id/start.json Start an experiment experiments_start

Starts a draft or paused experiment, so visitors of the original page begin to be sent to the variants. With starts_at in the future it waits for that time. Because it redirects real visitors, the MCP tool does not run it and responds with human_only and the page where a person starts it. The API runs it. Requires content.write.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/experiments/:id/start.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /sites/:site_id/experiments/:id.json Update an experiment experiments_update

Changes an experiment. Only the inputs you send change, and variants replaces the whole list. Once an experiment has started only name and send_to_winner can change, because new pages, shares or goals part way through would spoil the result: use experiments_duplicate and edit the copy. Requires content.write. Returns the experiment.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/experiments/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Exports

POST /workspaces/:workspace_id/exports.json Queue an export (built in the background) exports_create

Queues a data export that is built in the background and returns it right away with status queued. Choose it when you need a file (CSV, JSON, XLSX, NDJSON or a ZIP of dashboard panels), and use exports_preview first to check columns and size without creating anything. For lightweight numbers use the stats tools instead. Required inputs are workspace_id (path), site_id, from and to (YYYY-MM-DD, inclusive, in the export timezone). The kind decides what is exported and which options apply. aggregated (default) gives visit metrics per time bucket and uses grouping, dimensions (up to 6), metrics, filters and segment_id. pageviews, events and raw give one row per hit and use fields, filters and segment_id, require the Pro plan or above, and are limited to 400 days (raw to 93 days). imported gives imported daily stats and uses only dimensions. dashboard gives a ZIP of the overview panels (format zip only). Allowed file_format per kind is csv, json or xlsx for aggregated, pageviews, events and imported, csv or ndjson for raw, and zip for dashboard. Options that do not apply to the chosen kind are ignored. A workspace can have at most 5 exports queued or running at once. The response is the export with id (exp_...), status, row_count and expires_at (null until built). Poll exports_get until status is complete, then call exports_download. The requesting user gets an email when it is ready, a webhook export.ready is sent, and the action is written to the audit log. Event properties in the props column only include properties marked as safe to export (see event_properties_list).

Permission: analytics.export

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/exports.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /exports/:id.json Delete an export and its file exports_delete

Permanently deletes an export and its generated file. The download link stops working at once and this cannot be undone, but you can create the export again. Use it to remove a finished or failed export you no longer need, since files are also deleted automatically 7 days after they are built. The id comes from exports_list. Returns the deleted export object as it was before deletion.

Permission: analytics.export · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/exports/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /exports/:id/download.json Get a signed link to the file (valid 10 minutes) exports_download

Returns a short-lived download link for a finished export. Choose it after exports_get shows status complete. The response has url (a signed link that is valid for 10 minutes, fetch it right away), expires_at (when the link stops working, ISO 8601), filename and byte_size (bytes). It fails with a not found error code expired when the export is older than 7 days, and with a validation error when the export is still queued or running or when it failed (the error message says why). Call it again to get a fresh link as long as the export itself has not expired. The id comes from exports_list.

Permission: analytics.export

Example
curl https://privatusanalytics.com/exports/:id/download.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /workspaces/:workspace_id/exports.json List exports (the job queue) exports_list

Lists the exports of a workspace for the sites you can access, newest first. Use it to find an export id, check which exports are still running or to see what has expired. Filter with site_id, status (queued, running, complete, failed or expired) and kind (aggregated, pageviews, events, imported, raw or dashboard). Paginated with page and per_page (up to 100), and returns data (a list of exports, see exports_get for the fields) plus pagination meta. Needs the workspace_id from workspaces_list.

Permission: analytics.export

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/exports.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /workspaces/:workspace_id/exports/preview.json Preview an export: first 20 rows and the estimated row count exports_preview

Dry run of an export that creates nothing. It takes the same inputs as exports_create and returns kind, file_format, columns (the column names in order), rows (up to 20 sample rows as objects keyed by column) and estimated_rows (an approximate row count of the full file). For the dashboard kind the sample rows list each file name with its row count. Use it to validate dimensions, fields, filters and the date range, and to see how large the export would be before calling exports_create. It runs synchronously, so keep the date range reasonable. Validation errors are the same as for exports_create, including the Pro plan requirement for row level kinds. Nothing is stored and no email is sent.

Permission: analytics.export

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/exports/preview.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /exports/:id.json Get an export and its status exports_get

Returns one export, including its progress. Use it to poll an export started with exports_create until status is complete or failed. Statuses are queued, running, complete, failed and expired. Fields include id (exp_...), site_id (pa_...), kind, format, status, params (the stored definition of the export), row_count (null until complete), error (set when failed), completed_at, expires_at (files are kept 7 days after completion), download_url (set only while the file can be downloaded, use exports_download to get a signed link) and created_at. The id comes from exports_create or exports_list.

Permission: analytics.export

Example
curl https://privatusanalytics.com/exports/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Forms

POST /sites/:site_id/testimonials/forms.json Create a form testimonial_forms_create

One call carries the whole form: steps, questions, skip logic, consent wording and language. Anything left out starts from a sensible default in the form's language, and the slug comes from the name. The link is https://privatus.page/f/<slug>. Low-rating routing and custom CSS need the Business plan (402 plan_limit without). Video and audio answers are offered to the giver on Pro and up.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/forms.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/forms/:id/permanent.json Delete a form for good testimonial_forms_delete

Deletes the form, its submission records, its reward and its private feedback. The testimonials it collected are kept. It cannot be undone, so it is done by a person on the page. The API takes confirm (the form's name or id).

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/forms/:id/permanent.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/forms/:id.json Move a form to the trash testimonial_forms_trash

Its link responds that the form is closed until it is restored. The trash is emptied after 30 days. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/forms/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/forms/:id/duplicate.json Copy a form, optionally into another language testimonial_forms_duplicate

Copies every setting except the slug, the statistics and the reward.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/forms/:id/duplicate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/forms.json List the forms of a site testimonial_forms_list

Forms in the trash are listed with trashed true, and left out otherwise. Each form carries its settings, its reward and how many submissions it has had.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/forms.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/forms/:id/qr.json Get a form's QR code as SVG or PNG testimonial_forms_qr

The code holds the form's link with ?src=qr, so scans are counted as their own channel. A changed slug keeps a printed code working.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/forms/:id/qr.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/forms/:id/restore.json Restore a form from the trash. Its link works again testimonial_forms_restore

Takes a form out of the trash as it was, with its questions, submissions and private feedback. Its link (/f/<slug>) and its embeds take answers again. The server responds with 422 and the code not_in_trash for a form that is not in the trash.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/forms/:id/restore.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/forms/:id/share.json Get a form's link, QR code links and embed snippets testimonial_forms_share

Returns the link, the inline embed snippet and the popup embed snippet. The popup inputs (trigger, label, position, accent) change the popup snippet.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/forms/:id/share.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/forms/:id.json Get one form with every step, question, rule and setting testimonial_forms_get

Returns the whole form as testimonial_forms_create and testimonial_forms_update take it, with its slug, its link, its reward and how many submissions it has. A form in the trash is not found here: restore it first.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/forms/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/forms/:id.json Change a form. Only the inputs sent change testimonial_forms_update

A change to a step, a question or the consent wording raises the form's version, which every consent record stores. A changed slug leaves the old link redirecting. Send questions as the whole list.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/forms/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Funnels

POST /sites/:site_id/funnels.json Create a funnel funnels_create

Creates a funnel, a sequence of steps that visits move through, measured per visit. Pass name and steps, an ordered list of {type, value, match, name}. type is page, event or goal. value is a path for page steps (for example /pricing), a custom event name for event steps, or a goal id (goal_..., from goals_list) for goal steps. match is only for page steps and is exact, prefix, glob (* wildcard) or regex, defaulting to glob when the value has a * and exact otherwise. name is an optional label. Steps with an empty value are dropped. order_mode is strict (each step must come after the previous one, the default) or any (steps in any order). window is visit (the default) or day, and both measure within a single visit because no visitor id is stored. Plan limits apply. Free allows 3 funnels per site and 5 steps, Pro allows 10 steps and Business 20, and a funnel needs at least 2 steps. Returns the funnel with id (fun_...). Read results with funnels_get. Needs the content.write permission.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/funnels.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/funnels/:id.json Delete a funnel funnels_delete

Permanently deletes a funnel definition. No analytics data is touched, but the funnel and its settings cannot be recovered and there is no undo. Returns the deleted funnel. Ids (fun_...) come from funnels_list. Use funnels_update to change a funnel instead. Needs the content.write permission.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/funnels/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/funnels.json List funnels with entries, completions and rate funnels_list

Lists the funnels of a site with their headline results for the period, ordered by name. Each funnel has id (fun_...), name, steps, order_mode, window and stats {entered, completed, conversion_rate}, where entered is visits that reached step 1, completed is visits that reached the last step and conversion_rate is a percent (0 to 100). Paginated with page and per_page (max 100, default 25). Takes the shared period and filters inputs. For step-by-step detail use funnels_get, and use funnels_create to add one.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/funnels.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/funnels/:id.json Funnel report: step counts, conversion, drop-off, time between steps and where visits went instead funnels_get

The conversion report of one funnel for a period. Returns period, funnel_id, name, order_mode, window, steps, overall_conversion_rate, breakdown and, with compare, comparison. Each step has index (from 1), name, type, value, match, count (visits that reached it), step_conversion_rate (percent of the previous step), overall_conversion_rate (percent of step 1), dropoff and dropoff_rate (visits that left after this step), median_seconds_from_previous (null for step 1) and next_pages (top 5 pages visited next by dropped visits, with (exit) for those who left). Rates are percents from 0 to 100. Set breakdown to channel, device, country, utm_campaign, utm_source, browser or os to get the overall conversion per value (top 20). comparison has the other period's counts, overall_conversion_rate and change (percent, null when the earlier rate was 0). Page filters are ignored here because they would cut visits short, use a page step instead. Takes the shared period, compare, filters and segment inputs. Ids come from funnels_list.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/funnels/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/funnels/:id.json Update a funnel funnels_update

Changes a funnel. Only the inputs you send change. Sending steps replaces the whole list of steps, so send every step in order (format and step limits are the same as in funnels_create, free allows 5 steps, Pro 10 and Business 20, and goal steps must name goals of this site). You can also change name, order_mode (strict or any) and window (visit or day). Results are computed from the definition when read, so changing a funnel applies to all history straight away and past numbers shown earlier will differ. Returns the funnel. Ids come from funnels_list. Needs the content.write permission.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/funnels/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Goals

POST /sites/:site_id/goals/:id/archive.json Archive a goal (hidden from reports) goals_archive

Archives a goal. An archived goal is hidden from reports, does not count toward the plan's goal limit and keeps its definition, so it can be restored later with goals_unarchive. No data is deleted. Returns the goal with archived true and archived_at set. Archiving an already archived goal does nothing. Use goals_delete to remove it for good. Needs the content.write permission.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/goals/:id/archive.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/goals.json Create a goal goals_create

Creates a conversion goal. Goals are definitions applied when reports are read, so a new goal immediately counts matching events in all existing history. Required are name and kind. kind decides which other inputs apply. page needs pattern (a path, for example /thank-you, with match_type exact, prefix, glob or regex) and can use hostname. event needs event_name and can use conditions (property tests such as {key: plan, operator: is, value: pro}, all must match, operators is, is_not, contains, gt, lt). engagement needs pattern plus scroll_threshold (percent 1 to 100) and/or time_threshold_seconds (reached when either is met). outbound matches the link domain with pattern (for example stripe.com) and download matches the file with pattern (for example *.pdf). value_mode is none (default), fixed (uses fixed_value, a decimal amount in the site currency such as 49.5, stored as fixed_value_minor in cents) or revenue (the event's own revenue). count_mode is once_per_visit (default) or every. Free workspaces allow 10 active goals per site, paid plans are unlimited. Returns the goal with id (goal_...). Needs the content.write permission.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/goals.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/goals/:id.json Delete a goal (data is kept) goals_delete

Permanently deletes a goal definition. No event data is deleted, but the goal disappears from reports, and funnel steps, journeys and experiments that point to it stop working. There is no undo. To hide a goal and keep it, use goals_archive. Returns the deleted goal. Ids (goal_...) come from goals_list. Needs the content.write permission.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/goals/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/goals/:id/duplicate.json Copy a goal goals_duplicate

Copies a goal's definition into a new active goal. The copy is named name if you send it, otherwise the original name followed by (copy). It counts toward the plan's goal limit (10 active goals per site on Free). Returns the new goal with a new id. Use it to start a variation of a goal, then change it with goals_update. Needs the content.write permission.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/goals/:id/duplicate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/goals.json List goals with completions, conversions, rate and value for a period goals_list

Lists the goals of a site with their results for the period. Each goal is the definition (id goal_..., name, kind, match_type, pattern, hostname, event_name, conditions, scroll_threshold, time_threshold_seconds, value_mode, fixed_value_minor in cents, count_mode, archived, archived_at) plus stats {completions, conversions, conversion_rate, value, change}. conversion_rate is a percent (0 to 100), value is in minor units (cents) and change is the percent change in conversions against the previous period (null when the earlier count was 0). status filters to active (default), archived or all. Paginated with page and per_page (max 100, default 25). Use goals_get for the full report of one goal. Takes the shared period, compare and filters inputs.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/goals.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/goals/:id.json Goal report: metrics, chart, breakdowns and the journey to converting goals_get

One goal in depth for a period. Returns period, goal_id, goal (the definition), metrics, interval, series, breakdowns, journey and, with compare, comparison_period, comparison and change. metrics has completions (every matching event), conversions (visits with at least one completion), conversion_rate (percent from 0 to 100) and value (minor units, cents). conversion_rate divides by visits, or by visitors when the site is set that way. series is a list of {date, completions, conversions, conversion_rate} per time bucket (set interval to change it). breakdowns holds the top 10 by conversions for channel, utm_campaign, entry_page, country and device, each as {value, completions, conversions, conversion_rate}. journey shows converting_visits, median_seconds_to_convert, median_pages_before and pages_before (top 10 pages seen before converting, with visits and share). Use goals_list for all goals at once and funnels_get for multi-step flows. Takes the shared period, compare, filters and segment inputs.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/goals/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/goals/:id/unarchive.json Restore an archived goal goals_unarchive

Restores an archived goal so it shows in reports again. It counts toward the plan's goal limit again (10 active goals per site on Free), so the call fails with a plan limit error when the site is already full. Restoring a goal that is not archived does nothing. Returns the goal with archived false. Ids come from goals_list with status archived. Needs the content.write permission.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/goals/:id/unarchive.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /sites/:site_id/goals/:id.json Update a goal goals_update

Changes a goal's definition. Only the inputs you send change. The new definition applies to all history straight away, so past conversion numbers will change. The inputs are the same as in goals_create, and the same combinations apply, for example event_name and conditions only matter for event goals and scroll_threshold and time_threshold_seconds only for engagement goals. fixed_value is a decimal amount in the site currency (stored in cents). Sending conditions replaces all existing conditions. Returns the goal. Ids come from goals_list. Needs the content.write permission.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/goals/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Help and feedback

POST /help.json Send a question or feedback to the Privatus Analytics team support_message_send

The message is emailed to the team with your account, workspace and plan details attached. The reply and a receipt go to your account email. Up to 10 messages a day.

Permission: member

Example
curl -X POST https://privatusanalytics.com/help.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Imports

DELETE /sites/:site_id/imports/google/connection.json Disconnect your Google Analytics account and delete its stored access tokens imports_google_disconnect

Removes the stored Google Analytics connection of the calling member for this site's workspace, so GA4 imports can no longer read from Google until it is connected again in the app. Data that was already imported stays and is removed only by deleting its import with imports_delete. The grant is not revoked on the Google side. It fails with a validation error when there is no connection, or while one of the member's GA4 imports is queued or running. Returns an object with disconnected true. Connecting is done in the app (the Google sign in screen), there is no tool for it.

Permission: sites.manage

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/imports/google/connection.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/imports/google/properties.json List Google Analytics 4 properties to import imports_google_properties

Lists the Google Analytics 4 properties the calling member's connected Google account can read. Call it before imports_create with source ga4 to get the property_id to use. Returns a list of objects with id (like properties/123456789, pass it as property_id), name and account. It fails with a validation error when the member has not connected Google for this workspace (done in the app, there is no tool for it) or when Google rejects the request. It makes a live call to Google and changes nothing.

Permission: sites.manage

Example
curl https://privatusanalytics.com/sites/:site_id/imports/google/properties.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/imports.json Start an import (GA4 property or CSV upload) imports_create

Starts an import of historical data from another analytics tool into a site, processed in the background, and returns the import with status queued. Choose source ga4 to pull from the Google Analytics 4 API, which needs a Google connection made in the app, property_id (from imports_google_properties) and date_from. date_to defaults to the day before the site's first tracked event, or yesterday. Other sources (universal_analytics, fathom, simple_analytics, plausible, matomo, umami, clicky) take a CSV export uploaded as the file input (multipart form upload, up to 50 MB), which makes them impractical to start over MCP and best done in the app. dimension is only needed to override detection of what the CSV rows break down, for example Matomo Label exports. Imported rows never count toward the event allowance and only appear in stats when you pass include_imported true. A workspace can have at most 5 imports queued or running at once. Poll imports_get until status is complete or failed. Needs the sites.manage permission.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/imports.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/imports/:id.json Delete an import and all its imported data imports_delete

Permanently deletes an import together with every row it brought in and its uploaded file. Stats that included the imported data change right away when include_imported is on, and this cannot be undone, although you can import again. The id comes from imports_list and needs the site_id. Returns the deleted import. Needs the sites.manage permission. Data collected by the tracker is not touched.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/imports/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/imports.json List imports for a site imports_list

Lists the imports of a site, newest first. Use it to find an import id or check whether one is still running. Optionally filter by status (queued, running, complete or failed). Paginated with page and per_page (up to 100). Each item has id, site_id, source, status, date_from, date_to, rows_imported, error, completed_at and created_at. See imports_get for details and imports_create to start one.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/imports.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/imports/:id.json Get an import and its status imports_get

Returns one import of a site, used to poll an import started with imports_create. Fields are id (imp_...), site_id, source, status (queued, running, complete or failed), date_from and date_to (YYYY-MM-DD, null when unknown), rows_imported (count of imported daily rows), error (set when failed), completed_at and created_at. The id comes from imports_create or imports_list. Imported rows only show in stats when the stats call sets include_imported true.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/imports/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Live

GET /sites/:site_id/live.json Live visitors, activity stream and what is popular right now (last 30 minutes) live_get

What is happening on the site right now. Returns live_visitors (visitors in the last 5 minutes), window_minutes (30), sparkline (visits per minute for the last 30 minutes, oldest first, each {minute, visits}), feed (the latest activity), top_pages, top_referrers, top_countries (each up to 8 rows of {value, visits}), countries (every country with a visit, same shape, for the map) and top_events (up to 8 of {value, completions}) for the last 30 minutes. feed entries have occurred_at, kind, name, path, channel, referrer_host, country, device and browser. They are anonymous, with no IP, city or visitor trail. feed shows all hits by default, or only pageviews or events, with limit entries (default 50, up to the buffer size of 100). The numbers are not cached and take no period. Pass filters (same format as stats_aggregate) to narrow every number and the feed to the matching hits. Use live_wallboard for a compact multi-site view and stats_overview for a full period.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/live.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/live/wallboard.json Wallboard cards (live and today) for one or more sites live_wallboard

Compact big-number cards for one or more sites, built for an office screen. Returns rotate_seconds, generated_at and sites. Each site card has id (pa_...), name, domain, live_visitors (last 5 minutes), sparkline (per minute, last 30 minutes), visitors_today, pageviews_today, bounce_rate_today (percent, 0 to 100), top_pages, top_countries and top_referrers (5 rows each). Today means the site's own time zone and the numbers are read from live data. site_ids lists the sites to include (up to 12, only sites your token can open), otherwise the site in the path is used. rotate_seconds is clamped between 5 and 300 and only tells a screen how long to show each card. For a shareable public screen use wallboard_links_create. Use live_get for the full live view of one site.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/live/wallboard.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /wallboard/:token.json Wallboard for a wallboard token (no login needed) live_public_wallboard

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/wallboard/:token.json

Members

POST /workspaces/:workspace_id/invitations.json Invite people by email invitations_create

Invites one or more people to a workspace by email and sends each an invitation email with a link that is valid for 14 days. emails is a list of up to 50 addresses (a comma, space or semicolon separated string also works in the app form) and addresses that are already members are skipped, but it is an error when all of them are members. Inviting an address that already has a pending invitation replaces that invitation and sends a new email. role is one of the roles in members_list (default viewer, owner cannot be invited). Use role custom together with custom_role_id (the id from roles_list, Business plan). By default access covers all sites including future ones. To limit access set all_sites to false and list site_ids as public site ids (pa_...). Pending invitations count toward the plan's member limit (Free allows 1 member, the owner) and the call fails when the limit would be exceeded. Invitation emails are rate limited. You cannot grant a role with more permissions or sites than your own. Requires members.manage. Returns the created invitations (see invitations_list for fields).

Permission: members.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/invitations.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /workspaces/:workspace_id/invitations/:id.json Revoke a pending invitation invitations_delete

Revokes a pending invitation so its link no longer works, and frees the seat it held. The deletion cannot be undone, invite the person again with invitations_create. Take the id (a uuid) from invitations_list. A caller limited to some sites can only revoke invitations that grant no more than their own sites. Requires members.manage. Returns the revoked invitation.

Permission: members.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/workspaces/:workspace_id/invitations/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/invitations.json List pending invitations invitations_list

Lists the pending team invitations of a workspace, newest first and paginated. Accepted and revoked invitations are not listed (use members_list for people who joined), and expired ones stay until they are replaced or revoked. Requires members.manage. A caller limited to some sites only sees invitations within their reach. Each item has id (uuid, used by invitations_delete), email, role, custom_role (the custom role's name when role is custom, otherwise null), all_sites, site_ids (public site ids pa_..., empty when all_sites is true), invited_by (the inviter's email), expires_at and created_at.

Permission: members.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/invitations.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
DELETE /workspaces/:workspace_id/members/:id.json Remove a member (or leave the workspace) members_delete

Removes a member from a workspace, or lets the caller leave it when the id is their own membership. This is immediate and cannot be undone, the person loses access, their API tokens for this workspace are revoked and any SCIM link is removed. Their notes and past audit entries stay. The owner cannot be removed or leave, transfer ownership first with ownership_transfers_create. Removing someone else needs members.manage, and you cannot remove someone with more permissions or sites than you hold. Leaving needs no permission but a token that is limited in permissions or sites cannot be used to leave. The person can be invited again later with invitations_create. Take the id (the membership uuid) from members_list. Returns the removed member.

Permission: member · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/workspaces/:workspace_id/members/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/members.json List workspace members members_list

Lists the members of a workspace in the order they joined, paginated. Use query to search by name or email (partial, case-insensitive) and role to filter by role. Pending invitations are not included (use invitations_list). Each member has id (the membership uuid, used by members_update, members_delete and ownership_transfers_create), email, name (nullable), role, custom_role (name, null unless role is custom), custom_role_id, all_sites, include_future_sites, site_ids (public site ids pa_..., empty when all_sites is true), two_factor (whether the member has an authenticator app or a passkey), last_active_at (nullable) and created_at. Callers limited to some sites see only members within their reach.

Permission: member

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/members.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:workspace_id/members/:id.json Change a member's role or site access members_update

Changes a member's role and or site access in a workspace. Only the inputs you send change. To change the role send role (admin, editor, analyst, viewer or billing, and custom with custom_role_id from roles_list on the Business plan). The owner's role cannot be changed here, use ownership_transfers_create instead, and you cannot edit your own membership or someone who outranks you. To limit sites send all_sites false together with site_ids (public site ids pa_...), and add include_future_sites true to also grant sites created later. Send all_sites true to give access to every site. A site_ids list sent without all_sites false is an error for a member who currently has every site. You cannot grant more permissions or sites than you hold. Requires members.manage. Take the id from members_list. Returns the updated member.

Permission: members.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/members/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/ownership_transfer.json Transfer workspace ownership to another member (owner only, you become an admin) ownership_transfers_create

Transfers ownership of a workspace to another member and demotes the caller to admin, which is hard to undo because only the new owner can transfer it back. Only the current owner can call it, and a token must not be limited to certain sites or permissions. membership_id is the new owner's membership uuid from members_list (not a user id or email) and the person must already be a member, invite them first if they are not. The new owner gets role owner with access to all sites, and the owner role cannot be given through members_update. A Free workspace cannot be given to someone who already owns another Free workspace. Billing stays with the workspace. A token calling this needs members.manage. Returns the new owner's member record.

Permission: member · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/ownership_transfer.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Mobile app

GET /workspaces/:workspace_id/app.json Get what the mobile app needs to open a workspace mobile_bootstraps_get

Returns, for one workspace (path takes workspace_id), the caller's role and permissions there, features (which gated reports the plan includes: traffic_quality, bot_detection, external_experiments, campaign_costs, status_pages, traffic_rules, experiment_advanced (the advanced settings of an experiment) and survey_ai_themes (themes for the written answers of a survey) as true or false, and retention_months as a number of months or null for no limit), testimonial_moderation (true when the member's role lets the mobile app approve, reject and return a testimonial to pending), sites_count (the sites the caller can open) and min_app_version (the oldest version of the mobile app the API still serves). The mobile app reads it once when it opens a workspace, so it can show a report the plan lacks as not included instead of asking for it. Needs analytics.read.

Permission: analytics.read · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/app.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
DELETE /account/device.json Stop push notifications to the mobile app mobile_devices_delete

Removes the push registration of the calling mobile app login, so the device gets no more pushes. The login itself stays, and the app can register again. Logging out (POST /oauth/revoke with the refresh token) removes it too. Only a token of the mobile app can call it.

Permission: member · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/account/device.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /account/device.json Get the mobile app's push registration mobile_devices_get

Returns the device of the mobile app login that is calling: registered (false until the app registered a push token), platform, locale, timezone, app_version, kinds (every kind of push there is), muted_kinds and muted_site_ids (what the person switched off). Only a token of the mobile app can call it.

Permission: member · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/account/device.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /account/device.json Register the mobile app for push notifications mobile_devices_update

Registers the device of the calling mobile app login for push notifications, or changes what it gets. Only the inputs you send change. The first call needs platform and push_token. A device gets a push for uptime (a check went down or came back), alert (an alert triggered or resolved), site_audit (a site audit finished), experiment (an experiment has a result or ended) and usage (the monthly allowance is nearly or fully used), for every site its member can open, on every plan. muted_kinds and muted_site_ids switch kinds and sites off, and each replaces its whole list. Registering a push token that another login of the app holds moves it to this one. Only a token of the mobile app can call it. It is one of the two writes such a token may make.

Permission: member · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/account/device.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Notes

POST /sites/:site_id/notes.json Add a note to a site (use source "deploy" for release webhooks) notes_create

Adds an annotation to one site's charts, for example a launch, a campaign or a release. Use workspace_notes_create instead for a note that shows on every site. date defaults to today in the site's timezone, and end_date marks a range (YYYY-MM-DD). title is shown on the chart, body is plain text and url can point to a PR or changelog. color is one of neutral, brand, blue, amber, red or violet, default neutral. source is manual by default. Deploy integrations send source deploy, and then title defaults to a text built from version (a release version or commit) and color defaults to blue. Requires content.write. Returns the created note (see notes_list for fields).

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/notes.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/notes/:id.json Delete a note notes_delete

Permanently deletes a site note, which disappears from the charts and cannot be restored. The note must belong to this site, workspace-wide notes are deleted with workspace_notes_delete. Take the id (note_...) from notes_list. Requires content.write. Returns the deleted note.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/notes/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/notes.json List notes for a site (including workspace-wide notes) notes_list

Lists the annotations (notes) shown on a site's charts, newest date first and paginated. It returns the site's own notes plus workspace-wide notes that apply to every site. Filter with from and to (YYYY-MM-DD, a note matches when its date falls in the range), source (manual, deploy, system, import or incident) and query (a case-insensitive search of title, body and url). Use workspace_notes_list for the workspace view across all sites. Each note has id (note_...), site_id (the public site id pa_..., null for a workspace-wide note), date, end_date (null for a single day), title, body (nullable plain text), url (nullable), color (neutral, brand, blue, amber, red or violet), source and created_at. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/notes.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/notes/:id.json Get a note notes_get

Returns one note by its id (note_..., from notes_list) for a site, including workspace-wide notes that apply to the site. A note is a dated annotation shown on charts, for example a launch, a campaign or a deploy. Fields are id, site_id (null when workspace-wide), date and end_date (YYYY-MM-DD, end_date null for a single day), title, body (nullable), url (nullable), color (neutral, brand, blue, amber, red or violet), source (manual, deploy, system, import or incident) and created_at. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/notes/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/notes/:id.json Edit a note notes_update

Edits a site note, changing only the fields you send (date, end_date, title, body, url, color). The source cannot be changed. Sending an empty url removes the link, and a date of null is ignored. The note must belong to this site, workspace-wide notes are edited with workspace_notes_update. Take the id (note_...) from notes_list. Requires content.write. Returns the updated note.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/notes/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/notes.json Add a workspace-wide note (shown on every site) workspace_notes_create

Adds a workspace-wide annotation that appears on the charts of every site in the workspace, for example a company-wide launch or an outage. It is created without a site (site_id is null). Use notes_create for a note on a single site. date defaults to today in the workspace's timezone, end_date marks a range (YYYY-MM-DD), color is one of neutral, brand, blue, amber, red or violet. Requires content.write, and a caller limited to some sites cannot create workspace-wide notes. Returns the created note.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/notes.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /workspaces/:workspace_id/notes/:id.json Delete a workspace note workspace_notes_delete

Permanently deletes a workspace-wide note so it disappears from every site's charts, and it cannot be restored. Only notes with no site can be deleted here, use notes_delete for a site note. Take the id (note_...) from workspace_notes_list. Requires content.write, and a caller limited to some sites cannot delete workspace-wide notes. Returns the deleted note.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/workspaces/:workspace_id/notes/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/notes.json List notes across a workspace workspace_notes_list

Lists the annotations across a workspace, newest date first and paginated. It returns workspace-wide notes (which show on every site) plus the notes of every site the caller can access, so check site_id on each item (null means workspace-wide). Use notes_list for one site. Filter with from and to (YYYY-MM-DD), source (manual, deploy, system, import or incident) and query (searches title, body and url). Fields are id (note_...), site_id, date, end_date, title, body, url, color (neutral, brand, blue, amber, red or violet), source and created_at. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/notes.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:workspace_id/notes/:id.json Edit a workspace note workspace_notes_update

Edits a workspace-wide note, changing only the fields you send (date, end_date, title, body, url, color). Only notes with no site can be edited here, use notes_update for a site note. Take the id (note_...) from workspace_notes_list. Requires content.write, and a caller limited to some sites cannot edit workspace-wide notes. Returns the updated note.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/notes/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Notification channels

DELETE /channels/:id.json Delete a notification channel notification_channels_delete

Deletes a notification channel and removes it from every alert that used it, so those alerts stop notifying there. This cannot be undone and the alerts themselves are kept. The id (nch_...) comes from notification_channels_list. Returns the deleted channel.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/channels/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/channels.json List notification channels notification_channels_list

Lists the notification channels of a workspace (path takes workspace_id), sorted by name and paginated. A channel is a place alerts are delivered to. Each has id (nch_...), kind (email, slack, teams, discord, webhook, pagerduty, opsgenie or push), name, target (a masked summary such as the email addresses or the end of a webhook URL, secrets are never returned), last_used_at and created_at. Filter by kind. Use the ids as channel_ids in alerts_create and alerts_update. Callers limited to some sites only see channels used by alerts on their sites.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/channels.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /channels/:id/send_test.json Send a test message through a channel notification_channels_send_test

Sends a real test message through the channel (an email, a chat post, a webhook call and so on), so only use it when the user wants that. The call fails with an error naming the cause if delivery does not work, otherwise it returns the channel. Test sends are rate limited per workspace, and email channels also per outside address. Use it after notification_channels_create or notification_channels_update to confirm a URL or key is right.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/channels/:id/send_test.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /channels/:id.json Get a notification channel notification_channels_get

Returns one notification channel by id (nch_..., from notification_channels_list). The response has kind, name, target (a masked summary, secrets such as webhook URLs and keys are never returned), last_used_at and created_at. Use notification_channels_send_test to check that it delivers.

Permission: analytics.read

Example
curl https://privatusanalytics.com/channels/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /notifications/unsubscribe/:token.json Unsubscribe an address from one alert channel or uptime check with the token from an email notification_unsubscribes_create

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/notifications/unsubscribe/:token.json \
  -H "Content-Type: application/json" -d '{…}'
POST /push_subscription.json Register a browser for push notifications push_subscriptions_create

Registers the current browser for web push alerts, using the three values from the browser's PushSubscription (endpoint, p256dh and auth). A subscription receives alerts from every workspace the member belongs to, for every site, which is why an API token may only use this when it can read all of its workspace and the member has no other workspace. Calling it again with the same endpoint updates the keys and moves the subscription to the caller. The endpoint must be a public https push service URL. This only registers the browser, so alerts still need a notification channel of kind push (notification_channels_create). Returns endpoint_host (the push service's host name, not the full endpoint) and created_at. Sessions opened through a workspace identity provider cannot use it.

Permission: member

Example
curl -X POST https://privatusanalytics.com/push_subscription.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /push_subscription.json Remove a browser push registration push_subscriptions_delete

Removes one of the signed-in member's browser push registrations so that browser stops receiving alert notifications. Pass the same endpoint that was used with push_subscriptions_create. Fails with not found when no such subscription exists for the member. Returns the removed subscription as endpoint_host and created_at. It does not delete any notification channel. Subject to the same token restrictions as push_subscriptions_create.

Permission: member

Example
curl -X DELETE https://privatusanalytics.com/push_subscription.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Performance

GET /sites/:site_id/performance.json Web Vitals: p75 scorecard, daily trend and breakdowns by page, device, browser and country performance_report

Web Vitals report from real visitors. Returns period, metric, sample_rate, scorecard, trend and breakdowns. scorecard has one row for each of LCP, INP, CLS, FCP and TTFB with p75 (the 75th percentile, in milliseconds except CLS, which is a unitless score), samples, rating (good, needs_improvement or poor, null with no data), good, needs_improvement and poor (percent of samples), previous_p75, change (percent against the earlier period, null when unknown), thresholds [good limit, poor limit] and benchmark ({low, median, high, sites}: the 25th, 50th and 75th percentile of the p75 of all sites on Privatus Analytics over 28 days, null when there is none or the workspace switched benchmarks off). trend is the daily p75 of the chosen metric as {date, p75, samples}. breakdowns has page, device, browser and country, each with up to limit rows (default 10, max 100) of the chosen metric as {value, p75, samples, rating, slow_share, poor}, worst and busiest first. Choose metric with exactly one of LCP, INP, CLS, FCP or TTFB in capital letters (default LCP). sample_rate is the share of visits that report vitals, so counts are samples and not all visits. Takes the shared period, compare, filters and segment inputs. For one page's vitals use pages_detail.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/performance.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Plans

GET /plans.json List plans, prices and limits plans_list

Lists the public plans (free, pro, business and enterprise) with their limits and price tiers, so you can pick valid values for billing_checkout, billing_preview and billing_change_plan. Each plan has name, events (events a month included, null for tiered or custom plans), badge_bonus (extra monthly events from the badge, Free only, otherwise null), limits (an object of plan limits such as sites, member seats, history retention in months, goals per site, AI questions and API requests per hour), tiers (each with events a month and the monthly and yearly price in whole USD, including VAT or sales tax) and contact_sales (true for custom plans). The meta object adds currency, a note on yearly pricing, counted (what counts as a billable event) and not_counted. For one workspace's own plan use billing_get. Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/plans.json

Private feedback

GET /sites/:site_id/testimonials/forms/:form_id/feedback.json List the private feedback of a form, newest first testimonial_feedback_list

Private feedback is an answer with a rating of 1 to 3 on a form with low-rating routing on (Business and up). It is never a testimonial, never public and never counted.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/forms/:form_id/feedback.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/forms/:form_id/feedback/:id/mark_read.json Mark a private feedback answer as read, or as unread again testimonial_feedback_mark_read

Sets read_at on one private feedback answer, or clears it with read false. Marking an answer that is already read keeps the time it was first read. Nothing is sent to the person who wrote it.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/forms/:form_id/feedback/:id/mark_read.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/forms/:form_id/feedback/:id.json Get one private feedback answer testimonial_feedback_get

Returns the rating, the text, the name the person gave and when it was read. Private feedback is an answer with a low rating on a form with low-rating routing on. It is never a testimonial and never public.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/forms/:form_id/feedback/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Removal requests

POST /sites/:site_id/testimonials/removal_requests/:id/dismiss.json Dismiss a removal request testimonial_removal_requests_dismiss

Closes the request with a note and leaves the testimonial where it is shown. Nothing is deleted and no email is sent to the person who asked. The note is required. A request that is already decided responds with 422.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/removal_requests/:id/dismiss.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/removal_requests.json List the removal requests of a site testimonial_removal_requests_list

Open requests unless another status is asked for. The email the giver typed is returned only to a caller who may change content.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/removal_requests.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/removal_requests/:id/resolve.json Decide a removal request testimonial_removal_requests_resolve

trash moves the testimonial to the trash, which takes it off every widget, wall and public page at once and can be undone for 30 days. dismiss leaves it showing and needs a note.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/removal_requests/:id/resolve.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/removal_requests/:id.json Get one removal request testimonial_removal_requests_get

One request with the testimonial it is about, the note the person left, whether the email they typed matches the email on the testimonial, how many times they asked, and the decision once there is one. The email itself is returned only to a caller with content.write.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/removal_requests/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Rewards

POST /sites/:site_id/testimonials/settings/rewards/:id/copy.json Copy a form's reward to other forms of the same site testimonial_rewards_copy

Copies the title, the text, the code and the on or off state of this form's reward to each form in form_ids (1 to 100 forms of the same site, not in the trash). A reward the target form already has is replaced. The server responds with 422 and the code unknown_ids when an id is not a form of the site, and then nothing is copied.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/settings/rewards/:id/copy.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/settings/rewards.json List the reward of every form of a site testimonial_rewards_list

One row per form. A form with no reward set has enabled false.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/settings/rewards.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/settings/rewards/:id.json Set the reward a form shows on its thank-you step testimonial_rewards_update

The id is the form's id. The code is text you supply. Privatus Analytics does not create, check or expire it.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/settings/rewards/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Roles

POST /workspaces/:workspace_id/roles.json Create a custom role roles_create

Creates a custom role, a named set of permissions you can give to members through invitations_create or members_update with role custom and the role's id as custom_role_id. It needs the Business plan and members.manage. permissions must list at least one name from the allowed values (analytics.read, analytics.export, content.write, sites.manage, uptime.manage, members.manage, tokens.manage, billing.manage, workspace.manage) and you cannot include a permission you do not hold yourself. Returns the role (see roles_list for fields).

Permission: members.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/roles.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /workspaces/:workspace_id/roles/:id.json Delete a custom role (must be unused) roles_delete

Deletes a custom role. This cannot be undone and it fails while any member or pending invitation still uses the role, so move those people to another role first with members_update (find them with members_list). The built-in roles cannot be deleted. Take the id (uuid) from roles_list. Requires members.manage. Returns the deleted role.

Permission: members.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/workspaces/:workspace_id/roles/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/roles.json List custom roles roles_list

Lists the custom roles of a workspace, ordered by name. Custom roles are a Business plan feature that bundle permissions under your own name, and the built-in roles (owner, admin, editor, analyst, viewer, billing) are not listed in data: the response meta has built_in, an object with the permissions of each built-in role by its name. Each role has id (uuid, used as custom_role_id in invitations_create and members_update), name, permissions (names such as analytics.read, content.write, members.manage), members (the number of members who hold it) and created_at. Callers limited to some sites see only roles within their reach.

Permission: member

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/roles.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:workspace_id/roles/:id.json Update a custom role roles_update

Renames a custom role and or replaces its whole permission list. Only the inputs you send change, and permissions replaces the existing list rather than adding to it. The change applies at once to every member and pending invitation that holds the role. You can only edit a role whose permissions you fully hold, and a caller limited to some sites needs reach over every site of its holders. Requires members.manage. Take the id (uuid) from roles_list. Returns the updated role.

Permission: members.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/roles/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

SCIM

POST /scim/v2/Users.json Provision a user scim_users_create

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/scim/v2/Users.json \
  -H "Content-Type: application/json" -d '{…}'
DELETE /scim/v2/Users/:id.json Deprovision a user scim_users_delete

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/scim/v2/Users/:id.json \
  -H "Content-Type: application/json" -d '{…}'
GET /scim/v2/Users.json List provisioned users scim_users_list

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/scim/v2/Users.json
GET /scim/v2/Users/:id.json Get a provisioned user scim_users_get

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/scim/v2/Users/:id.json
PATCH /scim/v2/Users/:id.json Update a user (PATCH active, name) scim_users_update

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/scim/v2/Users/:id.json \
  -H "Content-Type: application/json" -d '{…}'

SSO

POST /workspaces/:workspace_id/sso/activate.json Activate a tested SSO configuration sso_activate

Turns SSO on for the workspace. The configuration must already have status tested, which happens after a successful trial login (started from the workspace SSO settings page in the app, it needs a browser sign-in and cannot be run from here), or already be active. Business plan only. After activation members can sign in with SSO, and setting enforced through sso_update then requires SSO for every login. It fails when the config is untested or the plan has no SSO. Requires workspace.manage. Returns the configuration with status active (see sso_get).

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/sso/activate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/sso/deactivate.json Turn SSO off (back to draft) sso_deactivate

Turns SSO off by returning the configuration to draft and clearing enforced, so members go back to the workspace's other login methods. Provider details are kept. Reactivating requires a new trial login and sso_activate. Make sure members have another way to sign in first. This works on any plan. Requires workspace.manage. Returns the configuration with status draft (see sso_get).

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/sso/deactivate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/sso/scim_token.json Generate a new SCIM token (shown once) sso_scim_token

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/sso/scim_token.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/sso.json Get SSO settings sso_get

Returns the workspace's single sign-on (SSO) configuration, or an empty draft when none exists. Fields are protocol (saml or oidc), status (draft, testing, tested or active), enforced (members must sign in through SSO), domains (email domains that use this SSO), idp_sso_url and idp_entity_id for SAML, oidc_issuer and oidc_client_id for OIDC, default_role and jit_provisioning (create members on first SSO login), and scim_enabled. Secrets are never returned, only idp_cert_present and oidc_client_secret_present booleans. SSO is a Business plan feature. Requires workspace.manage.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/sso.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:workspace_id/sso.json Create or update SSO settings sso_update

Creates or edits the workspace's SSO settings. Business plan only. Only the inputs you send change. For SAML send protocol saml with idp_sso_url, idp_entity_id and idp_cert (PEM text). For OIDC send protocol oidc with oidc_issuer, oidc_client_id and oidc_client_secret, which is write-only and an empty value keeps the existing secret. domains lists the email domains that use this SSO. default_role is the role given to members created by first-login provisioning or SCIM (not owner or custom) and you can only set a role you could grant yourself. Changing any identity provider detail sends an active or tested config back to draft and turns off enforced, so it must be tested again before sso_activate works. enforced true is only accepted while status is active. Requires workspace.manage. Returns the configuration (see sso_get).

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/sso.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/search.json Search report: clicks, impressions, CTR, position, top queries/pages, landing pages and opportunities search_report

Search performance from Google Search Console and Bing Webmaster, joined to the site's own landing page metrics. Choose provider: all (the default) adds every connected engine together, with position weighted by impressions and countries and devices from Google only, or google or bing alone and the usual period or custom from and to. limit (1 to 100, default 25) caps the rows in each table. The result has period, provider, connection (property, status, last_synced_at of the chosen engine, or null when it is not connected or provider is all), connections (each connected engine with provider, property, status and last_synced_at), latest_date (newest day with data), delayed (true when the range extends past latest_date), totals and series (per day) each with clicks, impressions, ctr (a percentage, not a fraction, 2 decimals) and position (average rank, 1 is best, null if unknown), breakdowns for query, page, country and device (rows of value plus the same four metrics), landing_pages (top pages with visits, bounce_rate and conversion_rate as percentages, and conversions), and opportunities (low_ctr_queries with at least 100 impressions and a CTR under 2 percent, and striking_distance pages averaging position 8 to 20). Country values are as the provider reports them. Use stats_breakdown for visitor data and this tool for what happens before the click. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/search.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
DELETE /sites/:site_id/search/connections/:provider.json Disconnect Search Console or Bing and delete its data search_disconnect

Disconnects a search provider (google or bing) from a site and permanently deletes its imported search data for that provider. This cannot be undone. The Google authorization is removed too once no site uses it. Fails if that provider is not connected. Returns the removed connection. Needs sites.manage.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/search/connections/:provider.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/search/sync.json Import search data now search_sync

Queues an immediate import of search data for a connected provider (google, the default, or bing) instead of waiting for the regular schedule. It returns right away (HTTP 202) with the connection and the import runs in the background. Poll search_report and watch connection.last_synced_at. Search engines report with a delay of a couple of days, so the newest days stay empty even after a sync. Fails if the provider is not connected. Needs sites.manage.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/search/sync.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Segments

POST /sites/:site_id/segments.json Save filters as a segment segments_create

Saves a set of filters as a named segment that can be reused on any stats tool through segment_id. Pass name and filters (all conditions must match, and an {any: [...]} group matches when one of several does, see the filters input for the dimension names and operators). The filters are checked, so an unknown dimension or a bad value returns a 422 with a message per row. Use segments_preview first to check what a filter set matches. shared is true by default and makes the segment visible to everyone with access to the site, false keeps it private to you. Returns the segment with id (seg_...), site_id, name, description, filters, summary (a plain English description), shared, mine and timestamps. Needs the content.write permission.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/segments.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/segments/:id.json Delete a segment segments_delete

Permanently deletes a saved segment. Dashboards and links that use it fall back to all traffic. No analytics data is touched and there is no undo. Returns the deleted segment. Ids (seg_...) come from segments_list. Needs the content.write permission.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/segments/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/segments.json List segments you can use on a site segments_list

Lists the saved segments of a site, ordered by name: the shared ones plus your own private ones. Each segment has id (seg_...), site_id, name, description, filters, summary, shared, mine and timestamps. Pass an id as segment_id to any stats tool (stats_aggregate, stats_breakdown, funnels_get and others) to apply the segment's filters. Paginated through the standard page parameters. Use segments_create to add one.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/segments.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/segments/preview.json Describe filters in plain English and count the visitors they matched in the last 30 days segments_preview

Checks a set of filters without saving anything. Returns summary (the filters in plain English), visitors (how many visitors matched in the last 30 days, null when the filters are invalid), period (always 30d) and filters (the cleaned, canonical form that would be stored). Filters use the same format as segments_create, including {any: [...]} groups and optional spelled-out names for countries, channels and goals, which are converted to codes and ids. Invalid filters return a 422 that names the row. Use it to try filters before segments_create, and stats_aggregate with filters for numbers over another period.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/segments/preview.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/segments/:id.json Get a segment segments_get

Returns one saved segment: id (seg_...), site_id, name, description, filters (the stored conditions), summary (a plain English description such as Visitors from the United States on mobile), shared (visible to everyone with site access), mine (true when you created it) and timestamps. You can read shared segments and your own private ones. Ids come from segments_list. To count how many visitors a set of filters matches, use segments_preview.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/segments/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/segments/:id.json Rename a segment or change its filters or sharing segments_update

Renames a segment, replaces its filters or changes who can see it. Only the inputs you send change. Sending filters replaces all existing filters, in the same format and with the same checks as segments_create. shared false makes it private to its creator. description can be cleared by sending it empty. Everything that uses the segment, such as stats calls with its segment_id, changes straight away. Returns the segment. Ids come from segments_list. Needs the content.write permission.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/segments/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Sharing

GET /badge/:site_id.json Visitor counter badge (SVG) for a public site badges_get

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/badge/:site_id.json
GET /share/:token/breakdown/:dimension.json Top values of a shared dimension shares_breakdown

Top values of one dimension behind a share link, the shared equivalent of stats_breakdown. Path takes token (the last part of the share URL) and dimension, which must belong to a section the link shares, for example sources shares channel, referrer and referrer_url, pages shares page, entry_page, exit_page and hostname, campaigns shares the utm_ dimensions, locations shares country and language, technology shares device, browser, os and screen, conversions shares goal and events shares event. A dimension outside the link's sections is refused with a validation error. limit is 1 to 100 rows (default 10). Rows have the same fields and units as stats_breakdown for that dimension. The link's locked period and filters win over period, from, to and filters, and filters from the caller are ignored when the link does not allow them. A password link needs the X-Share-Password header.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/share/:token/breakdown/:dimension.json
GET /share/:token.json Shared dashboard overview for a share token shares_overview

Reads the dashboard behind a share link for people who have no account. The path token is the last part of a share URL (/share/<token>) and is the only credential, a password link also needs the X-Share-Password header, and an sso link needs a signed in member. It also accepts a site id (pa_...) when the site has its public dashboard turned on. Returns the same data as stats_overview, limited to what the link shares. That is period, metrics (visitors, visits, pageviews, views_per_visit, bounce_rate as a percent, visit_duration in milliseconds, revenue in minor units), the chart series for interval, comparison data when compare is set, and panels of top values only for the shared sections. It adds site (id, name, domain, timezone), sections and locked_period. live_visitors appears only when the link shares live. A locked period and locked filters on the link override period, from, to and filters. Use shares_breakdown for more rows of one dimension. For your own sites use stats_overview.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/share/:token.json

Site audit

GET /sites/:site_id/audit/issues.json List the issues of a site audit, one row per check site_audit_issues_list

Lists the issues of one audit, one row per check that flagged something, worst first (errors, then warnings, then notices, each by URLs affected). Each row has check_key (use it with site_audit_issues_get and the mute tools), title, category, severity (error, warning or notice), affected (URLs flagged, muted ones excluded), added and fixed (changes since the previous audit), and pageviews (visits to affected pages in the 30 days before the audit). Defaults to the latest finished audit, or pass audit_id. Filter with severity, category and new_only (only checks with URLs that are new since the previous audit). Paginated with page and per_page (max 100, default 50). The response meta has the pagination plus audit (id, finished_at and created_at of the audit listed, null when the site has none). To see which URLs a check flagged use site_audit_issues_get. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/audit/issues.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/audit/issues/:check_key.json Get one site audit check: why it matters, how to fix it and the affected URLs site_audit_issues_get

Details of one check in an audit, by check_key from site_audit_issues_list (for example title_missing). Returns audit_id, issue (the same row as in the list), why (what the problem means), fix (how to fix it), muted (true if the whole check is muted for the site) and urls, the flagged URLs with page_id (use it with site_audit_pages_get), url, pageviews, detail (extra facts about the finding, may be empty), is_new and muted. sort is pageviews (default, busiest first) or url. Paginated with page and per_page (max 100, default 25). Defaults to the latest finished audit, or pass audit_id. Muted URLs sort last. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/audit/issues/:check_key.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/audit/mutes/bulk.json Mute a site audit check on several URLs, or several whole checks, in one call site_audit_mutes_bulk

Mutes many findings in one request, all or nothing. Use one of two shapes. Either send check_key plus urls (up to 100 URLs) to mute one check on several pages, or send check_keys (up to 100) to mute whole checks for the site. Do not mix urls with check_keys. An unknown check or an invalid URL fails the whole request and mutes nothing. Already muted items are returned as they are. The latest audit's counts and score are refreshed once at the end. Returns the list of mutes. Undo each with site_audit_mutes_delete. Needs content.write.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/audit/mutes/bulk.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/audit/mutes.json Mute a site audit check, for the whole site or one URL site_audit_mutes_create

Mutes one check for the site, or for one URL only. check_key comes from site_audit_issues_list. Leave url out to mute the check everywhere, or pass a full URL (such as https://example.com/old) to mute it on that page only, where the URL is normalized the way the crawler does. The latest audit's counts and score update at once. Muting the same thing twice returns the existing mute instead of an error. To mute many at once use site_audit_mutes_bulk, and to undo use site_audit_mutes_delete. Needs content.write.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/audit/mutes.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/audit/mutes/:id.json Unmute a site audit check site_audit_mutes_delete

Removes a mute, found by its id from site_audit_mutes_list, so the check or URL counts in the audit score again. The latest audit is recounted immediately. This only removes the mute and does not delete audit data. Returns the removed mute. Needs content.write.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/audit/mutes/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/audit/mutes.json List the checks a site has muted site_audit_mutes_list

Lists the checks muted for the site, newest first. A mute hides a check either for the whole site (url is null) or for one URL, so those findings stay out of counts and the score though audits still record them. Each row has id (a mute id, use it with site_audit_mutes_delete), site_id, check_key, title, url (null for a site-wide mute), muted_by (the member name, null if unknown) and created_at. Filter with check_key. Paginated with page and per_page (max 100). Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/audit/mutes.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/audit/notifications.json Turn your audit summary email for a site on or off site_audit_notifications_update

Turns the weekly audit summary email on or off for the calling member on one site. It is a personal preference and does not affect other members. enabled true sends the member the summary after audits of this site, false stops it. Returns site_id, enabled and all_sites_enabled (false when the member switched off audit emails for every site in their account settings, which overrides this). Any member with analytics.read can change their own setting.

Permission: analytics.read

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/audit/notifications.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/audit/pages.json List and filter the pages of a site audit site_audit_pages_list

The page explorer, listing the URLs of one audit (the latest finished one by default, or audit_id). kind is page (default, HTML documents), resource (images, scripts, stylesheets) or external (links to other sites). Filter by state (queued, fetched, skipped, blocked, failed), status_class (2xx, 3xx, 4xx or 5xx), indexable, in_sitemap, has_issues (counting unmuted issues only), depth (clicks from the start page) and query (text in the URL or title). sort is pageviews (default), inlinks, depth, response_ms (slowest first) or word_count (fewest first). Each row has id (a UUID for site_audit_pages_get), url, path, kind, state, status, depth, indexable, indexability_reason, title, word_count, response_ms, cache_status, inlinks, pageviews, in_sitemap and issues (count). Paginated with page and per_page (max 100). The response meta has the pagination plus audit (id, finished_at and created_at of the audit listed, null when the site has none). Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/audit/pages.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/audit/pages/:id.json Get one audited page: its metadata, issues, links in and links out site_audit_pages_get

Everything the audit stored about one URL, by id (a UUID from site_audit_pages_list or from the urls of site_audit_issues_get). Returns id, audit_id, audited_at (when that audit finished), page (url, status, redirect_to, content_type, bytes, response_ms, title, meta_description, h1, canonical, robots_meta, indexable and indexability_reason, word_count, lang, hreflang, schema_types, social tags, images_count, images_missing_alt, link counts, link_score, in_sitemap, has_tracker, pageviews and search_clicks, fetched_at and more), redirect_target_id (the page it redirects to within the audit, or null), issues (check_key, title, category, severity, detail, is_new, muted), and inlinks and outlinks (up to 100 each with page_id, url, status, kind, anchor, nofollow, count). Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/audit/pages/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/audit/settings.json Get a site's audit settings site_audit_settings_get

Returns the site's audit settings. Fields are site_id, enabled (scheduled audits on or off, on by default), include_subdomains, check_external_links, exclude_paths, ignored_params, extra_start_urls, next_run_at (when the next scheduled audit is due, null if none), page_limit (pages per audit on the current plan, null when unlimited) and interval_days (days between scheduled audits on the plan). A site that never changed its settings returns the defaults. Change them with site_audit_settings_update. Needs sites.manage.

Permission: sites.manage

Example
curl https://privatusanalytics.com/sites/:site_id/audit/settings.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/audit/settings.json Change a site's audit settings site_audit_settings_update

Changes how the site is audited. Only inputs you send change. enabled turns scheduled and manual audits on or off (turning it on schedules the next audit one plan interval away). include_subdomains and check_external_links are booleans. The three lists replace the stored list, they are not merged, so send the full list. exclude_paths are path prefixes the crawler skips and each must start with a slash (up to 50). ignored_params are query parameter names that never make a different page, such as utm_source or sessionid (up to 50, letters, digits, dot, dash, underscore). extra_start_urls are full http or https URLs to crawl in addition to the home page (up to 20). A list can also be sent as text with one entry per line. Returns the updated settings. Needs sites.manage.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/audit/settings.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /audit/unsubscribe/:token.json Unsubscribe from site audit summary emails with the token from an email site_audit_unsubscribes_create

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/audit/unsubscribe/:token.json \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/audit/cancel.json Cancel the site audit that is running site_audit_cancel

Stops the audit that is running or queued for the site. The audit is marked canceled and nothing from the unfinished crawl is reported. A canceled audit does not use up the Free plan's monthly allowance. Fails with a validation error if no audit is live. Returns the canceled audit. Needs content.write.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/audit/cancel.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/audit/compare.json Compare two site audits: URLs added and fixed per check site_audit_compare

Compares two audits of the site, check by check. audit_id is the newer audit and base_id the older one. Both default sensibly (the latest finished audit against the finished audit before it), and the tool swaps them if you pass them in the wrong order. Returns audit and base (each an audit summary, base null if there is nothing earlier) and checks, one object per check whose URLs changed, with check_key, title, category, severity, base_affected, affected, added and fixed (URL counts), sorted errors first. Checks with no change are left out. For two consecutive audits added and fixed are exact, for audits further apart they are the net change. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/audit/compare.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/audit.json Start a site audit now site_audit_run

Starts a new crawl and SEO audit of the site now. It crawls the site's public pages in the background, so this returns immediately with the new audit in status queued. Poll site_audit_get (live field) or site_audit_history until the status is complete. Refused with a validation error when the audit is turned off in site_audit_settings_update, when another audit is already queued or running, when a manual audit was started in the last 24 hours, and on the Free plan when any audit ran in the last 30 days (failed and canceled ones do not count). Unverified sites with no events are also limited to one audit a day per domain. Pages crawled are capped by the plan (Free 100, Pro 1,000, Business 10,000). Check can_run in site_audit_get first. Needs content.write.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/audit.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/audit/history.json List a site's audits, newest first site_audit_history

Lists the site's audits, newest first, each with id (aud_...), status (queued, running, analyzing, complete, partial, failed or canceled), trigger (first, scheduled or manual), score (0 to 100, null until finished), score_band, page_limit, pages_crawled, urls_discovered, errors, warnings, notices, new_issues, fixed_issues, failure (reason when failed, else null) and the timestamps. Filter with status. Paginated with page and per_page (max 100). Use the ids with site_audit_get, site_audit_compare and the issues and pages tools. For the current state of one audit use site_audit_get. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/audit/history.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/audit.json Get the site audit overview: score, counts, top issues by traffic and the audit running now site_audit_get

The SEO site audit overview, the best first call for audit questions. By default it describes the latest finished audit, or pass audit_id (aud_..., from site_audit_history) for an older one. The result has audit (the finished audit with score 0 to 100, score_band, score_delta, counts of errors, warnings and notices, pages_crawled, page_limit, capped, summary and robots details, or null if none has finished), live (the audit running now, or null), last (the newest audit whatever its status, as id, status, failure, finished_at and created_at, or null), enabled and next_run_at (the schedule), can_run with run_blocked (live, disabled or too_soon) and run_allowed_at (when site_audit_run is allowed again), changes (added and fixed URL counts per severity), trend (up to 30 recent scores as id, finished_at, score), top_issues (up to 10 checks ranked by severity then pageviews of affected pages) and email_enabled (your own summary email preference). Follow with site_audit_issues_list for every issue and site_audit_pages_list for URLs. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/audit.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Sites

POST /sites/:site_id/data_reset.json Delete all events and visits in a date range (runs in the background, cannot be undone) sites_reset_data

Irreversibly deletes a site's pageviews, custom events and visits for a date range, and cannot be undone. Use it to clear test traffic or remove a bad period. It does not delete the site, its goals or its settings (use sites_delete for the site). from and to are YYYY-MM-DD dates, both inclusive and read in the site's timezone, and to must not be before from. confirm must be the site's domain exactly as shown by sites_get (case does not matter). The request is accepted and returns immediately with status queued, site_id, from and to. The deletion runs in the background in batches, so numbers in the stats tools may take a little while to settle, and there is no status endpoint to poll, check the stats tools until the range is empty. The action is recorded in the audit log. Requires the sites.manage permission.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/data_reset.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/filter_stats.json Filtered traffic summary: bots, traffic rules or PII redactions by reason and country (counts only) sites_filter_stats

Counts of hits that were dropped or cleaned before storage, over the last days. Use it to see how much bot traffic, rule-blocked traffic or redaction a site has. Choose kind with bot (hits dropped as bots), rule (hits dropped by traffic rules) or redaction (values redacted by the personal data scrubber). Returns kind, days, total, by_reason, by_country and by_day. by_reason and by_country are lists of {value, count} (up to 50, largest first, value null when unknown) and by_day is a list of {date, count}. days is clamped between 1 and 395 and defaults to 30. These are aggregate counts only. No IP addresses, user agents or redacted values are stored, so nothing can be traced to a person. These hits are not counted as events for billing. Use traffic_rules_list to see or change the rules.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/filter_stats.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/access.json List members and whether they can see this site sites_access_list

Shows which workspace members can see one site. Every member of the workspace is listed in join order, not paginated. Fields are membership_id (the uuid used by sites_access_update), email, name, role, access (true when the member can see this site) and locked (true for owners and admins, who always have access and cannot be changed). Other members have access when they have all sites or an explicit grant for this site. Use members_list to see a member's full site list. Requires members.manage.

Permission: members.manage

Example
curl https://privatusanalytics.com/sites/:site_id/access.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/access.json Grant or remove one member's access to this site sites_access_update

Grants or removes one member's access to one site. membership_id comes from sites_access_list (or members_list) and access is true to grant or false to remove. Owners and admins always have access and cannot be changed. Removing access from a member who had all sites switches them to an explicit list of every other site in the workspace and turns off their include_future_sites, so they will not get sites created later. Granting to someone who already has all sites changes nothing. You cannot change a member who holds permissions you lack or sites beyond yours. Requires members.manage. Returns that member's row in the same shape as sites_access_list.

Permission: members.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/access.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/favorite.json Star a site (pinned first on All sites) sites_favorite

Stars a site for the calling user so it is listed first by sites_list and on the All sites page. It only affects the caller, never other members, and starring an already starred site changes nothing. The call needs no input beyond the site id. Use sites_unfavorite to remove the star. Requires analytics.read. Returns the site (see sites_get), with favorite true.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/favorite.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/favorite.json Unstar a site sites_unfavorite

Removes the calling user's star from a site, so it is no longer pinned first in sites_list. It only affects the caller and does nothing if the site was not starred. Use sites_favorite to star it again. Requires analytics.read. Returns the site (see sites_get), with favorite false.

Permission: analytics.read

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/favorite.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/setup.json Installation status and snippet for a site sites_setup_get

Returns what is needed to install tracking on a site and whether data has arrived. Fields are site_id, snippet (the ready-to-paste HTML script tag with every tracker module enabled), receiving_data (true once the first event has been received) and first_event_at (ISO 8601, null until then). Poll this after installing to see if events are coming in. Use tracking_snippet instead when you want a customized snippet (chosen modules, manual pageviews, SPA mode, masks) and tracking_verify to fetch the live page and check the script is present. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/setup.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /transfers/:token/accept.json Accept a site transfer into one of your workspaces site_transfers_accept

Accepts a site transfer invitation and moves the site into one of the caller's workspaces. The token path parameter is from the invitation link, and workspace_id is the destination workspace (ws_...) in which the caller has sites.manage. The caller's own email must match the address the invitation was sent to, and the destination cannot be the workspace the site came from. The site keeps its id, settings, goals and data, but per-member access grants are cleared and its shared links move to the new workspace. It fails when the invitation is no longer pending or has expired, when the destination is at its plan's site limit, or when it already has an active site with the same domain. The move cannot be undone except by transferring back. A token calling this must belong to the destination workspace and have sites.manage. Returns the transfer with status accepted.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/transfers/:token/accept.json \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/transfer.json Start transferring a site to another workspace (emails the recipient) sites_transfer_start

Starts moving a site to another workspace by emailing the recipient an invitation link that is valid for 7 days. Nothing moves until the recipient signs in with that email and accepts (site_transfers_accept), so the site stays here until then. Starting a new transfer cancels any earlier pending one for the same site. email must be a valid address and the recipient picks the destination workspace. Transfer emails are rate limited. Use sites_transfer_cancel to withdraw it. Requires sites.manage. Returns the transfer with site_id, site_domain, from_workspace, to_email, status pending and expires_at.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/transfer.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/transfer.json Cancel a pending site transfer sites_transfer_cancel

Cancels the site's pending transfer invitation so its link stops working. It fails with a validation error when no transfer is pending. A transfer that was already accepted cannot be canceled. Requires sites.manage. Returns the transfer with status canceled.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/transfer.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /transfers/:token.json Get a pending transfer from its emailed token site_transfers_get

Shows a site transfer invitation from the token in its link (the token path parameter, from the invitation email sent by sites_transfer_start). The caller must be signed in or use an API token. Returns site_id, site_domain, from_workspace (the sending workspace's name), to_workspace_id (null until accepted), to_email (the invited address), status (pending, accepted or canceled) and expires_at. Invitations expire 7 days after they are sent. Use site_transfers_accept to accept it.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/transfers/:token.json
POST /workspaces/:workspace_id/sites.json Add a site sites_create

Adds a website to a workspace and returns it, including the tracking snippet to install. The workspace's plan limits the number of sites (Free allows 3) and the call fails with a plan-limit error when it is reached. domain is required and is normalized (for example a full URL or www prefix is reduced to the hostname). name defaults to the domain, timezone defaults to the workspace's timezone (an IANA name such as Europe/Berlin) and currency to the workspace currency (a 3-letter ISO 4217 code such as USD). Side effects are a site.created notification to the workspace's notification channels, a first SEO site audit and domain rating check being queued, and an email with the tracking code to the member who created it (with reminders while no data arrives). Members limited to selected sites who opted into future sites get access to it. Requires sites.manage. Follow up with tracking_snippet or sites_setup_get to install, and tracking_verify to check.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/sites.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:id.json Delete a site (purged after 7 days) sites_delete

Deletes a site. It is a soft delete, the site stops collecting and disappears from lists immediately, and its data is permanently purged after 7 days, so treat it as irreversible and contact support quickly if it was a mistake. confirm must be the site's domain (case does not matter). Pending transfers for the site are canceled and a site.deleted notification is sent to the workspace's notification channels. To clear only some traffic and keep the site use sites_reset_data. Requires sites.manage. Returns the site.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/sites.json List sites in a workspace sites_list

Lists the sites of a workspace that the caller can access, paginated, with the caller's starred sites first. Use query to search domain or name (partial, case-insensitive), label to keep sites that carry one label, and sort as position (the saved order, the default), name or visitors (visitors in the last 30 days, most first). The response meta has per-page pagination plus stats, an object keyed by site id with visitors (recent unique visitors), change (percent change against the previous period), sparkline (a list of daily values) and live (current visitors), totals across the page, and labels (every label of the sites the caller can access, sorted). Each item has the same fields as sites_get. This is the starting point for finding the site ids (pa_...) used by every other site tool. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/sites.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:id.json Get a site and all its settings sites_get

Returns one site's settings and status by its public id (pa_..., from sites_list). Key fields are id, workspace_id (ws_...), domain, name, extra_domains and multi_domain_mode (combined or separate), timezone (IANA name), currency (ISO 4217), labels, privacy options (honor_dnt, honor_gpc, pii_scrubber, redaction_rules, allowed_params, path_masks), retention_months (null means keep all data), bot_filtering, traffic_rule_modes (an object mapping a rule list to block or allow), vitals_sample_rate (a percentage), conversion_denominator (visits or visitors), public_dashboard and public_indexable, ingest_key_prefix (null when the site has no crawler log key), verified_at, first_event_at and last_event_at (null until data arrives), domain_rating (nullable), snippet (the HTML tracking tag), favorite (whether the caller starred it) and created_at. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:id.json Update site settings sites_update

Updates settings of a site, changing only the fields you send. Name and identity fields are name, domain (changing it re-checks the domain rating), extra_domains (a list of hostnames) and multi_domain_mode (combined counts one visitor across domains, separate keeps them apart). timezone is an IANA name and currency a 3-letter ISO 4217 code. Privacy fields are honor_dnt, honor_gpc, pii_scrubber, redaction_rules (a list of objects with name and pattern, pattern a regular expression), allowed_params (query parameters to keep), path_masks (a list of objects with pattern and replacement) and bot_filtering. Lists replace the whole existing list. retention_months must not exceed the plan's retention (Free 3 months). traffic_rule_modes maps a rule list (hostnames, paths, referrers, countries, ip_ranges, user_agents or events) to block or allow. vitals_sample_rate is a percentage and the plan caps it (10 on Free, 100 on paid plans). conversion_denominator is visits or visitors. public_dashboard makes stats viewable by anyone with the link and public_indexable lets search engines index that page. Changes take effect for new events within moments. Requires sites.manage. Returns the updated site.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sponsor/:token.json A sponsor report, by its link's token ad_reports_get

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/sponsor/:token.json

Status pages

GET /status/:slug/confirm/:token.json Confirm a subscription from the emailed link public_status_pages_confirm

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/status/:slug/confirm/:token.json
GET /status/:slug.json Public status page: current status, 90-day uptime bars, incidents and posts public_status_pages_get

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/status/:slug.json
POST /status/:slug/subscribers.json Subscribe an email address (double opt-in) public_status_pages_subscribe

Browser clients send a Cloudflare Turnstile token as `cf-turnstile-response`. Requests with an API token need none.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/status/:slug/subscribers.json \
  -H "Content-Type: application/json" -d '{…}'
POST /status/:slug/unsubscribe/:token.json Unsubscribe with the emailed token public_status_pages_unsubscribe

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/status/:slug/unsubscribe/:token.json \
  -H "Content-Type: application/json" -d '{…}'
POST /status_pages/:status_page_id/posts.json Post an incident update (emails subscribers) status_page_posts_create

Publishes an incident update on a status page and, by default, emails its confirmed subscribers. Choose it to announce or update an incident, and use status_page_posts_update to move an existing incident along (for example to resolved) instead of creating a new post. Required is title (max 200 characters). status is one of investigating (default), identified, monitoring or resolved. body is plain text (max 10,000 characters), posted_at is an ISO 8601 time that defaults to now, and notify (default true) controls the email. Emails are only sent for a published page and are rate limited per page, over the limit the post is still published but nobody is emailed. The status_page_id comes from status_pages_list. Needs the uptime.manage permission. Returns the post with id (spp_...), title, body, status, posted_at and updated_at.

Permission: uptime.manage

Example
curl -X POST https://privatusanalytics.com/status_pages/:status_page_id/posts.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /status_pages/:status_page_id/posts/:id.json Delete a status page post status_page_posts_delete

Permanently deletes an incident post from a status page. It disappears from the public page at once and this cannot be undone. No email is sent. The id comes from status_page_posts_list. Needs the uptime.manage permission. Returns the deleted post.

Permission: uptime.manage

Example
curl -X DELETE https://privatusanalytics.com/status_pages/:status_page_id/posts/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /status_pages/:status_page_id/posts.json List posts on a status page status_page_posts_list

Lists the incident posts of a status page, newest first by posted_at. Use it to find a post id or see the incident history. Optionally filter by status (investigating, identified, monitoring or resolved). Paginated with page and per_page (up to 100). Each post has id (spp_...), title, body (null when empty), status, posted_at and updated_at. The status_page_id comes from status_pages_list.

Permission: analytics.read

Example
curl https://privatusanalytics.com/status_pages/:status_page_id/posts.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /status_pages/:status_page_id/posts/:id.json Edit a status page post status_page_posts_update

Edits an incident post. Only the inputs you send change, so send just status to resolve an incident. Subscribers are emailed only when the status actually changes and notify is not false (default true). Editing the title or body alone sends no email. Emails go to confirmed subscribers of a published page and are rate limited per page. Valid status values are investigating, identified, monitoring and resolved. The id comes from status_page_posts_list. Needs the uptime.manage permission. Returns the updated post.

Permission: uptime.manage

Example
curl -X PATCH https://privatusanalytics.com/status_pages/:status_page_id/posts/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /status_pages/:status_page_id/subscribers/:id.json Remove a subscriber status_page_subscribers_delete

Removes an email subscriber from a status page so they stop receiving incident and uptime emails. This cannot be undone, but the person can subscribe again on the public page. The id is the subscriber id from status_page_subscribers_list (a plain UUID, not a prefixed id). Needs the uptime.manage permission. Returns the removed subscriber.

Permission: uptime.manage

Example
curl -X DELETE https://privatusanalytics.com/status_pages/:status_page_id/subscribers/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /status_pages/:status_page_id/subscribers.json List subscribers of a status page status_page_subscribers_list

Lists the email subscribers of a status page, sorted by email. Use confirmed true to see only people who confirmed their address (only they receive emails) or confirmed false for pending ones. Paginated with page and per_page (up to 100). Each item has id (a UUID, used by status_page_subscribers_delete), email, confirmed_at (null while pending) and created_at. The status_page_id comes from status_pages_list. Needs the uptime.manage permission and returns personal data (email addresses). Needs the Business or Enterprise plan (402 plan_limit otherwise).

Permission: uptime.manage

Example
curl https://privatusanalytics.com/status_pages/:status_page_id/subscribers.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /workspaces/:workspace_id/status_pages.json Create a status page status_pages_create

Creates a public status page for a workspace that shows chosen uptime checks, incident posts and an email signup. Required is name (max 100 characters). slug (lowercase letters, digits and hyphens, unique across all workspaces) defaults from the name and the page is served at /status/<slug>. check_ids lists the uptime checks to show (upc_... ids from uptime_checks_list) and only checks created with public true are accepted. published defaults to true, so the page is visible to everyone immediately. Optional branding is description (max 2,000 characters), logo_url (must be https), brand_color (hex like #06755b) and custom_domain (a hostname you point a CNAME at the app, unique). Use status_page_posts_create to publish incidents afterwards. Needs the uptime.manage permission. Returns the page with id (sp_...), slug, check_ids and subscribers (count of confirmed subscribers). The plan sets how many pages a workspace has: none on Free (402 plan_limit), 1 on Pro and 10 on Business. A custom_domain and email subscribers need the Business or Enterprise plan. Pages over the plan's count are kept and offline.

Permission: uptime.manage

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/status_pages.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /status_pages/:id.json Delete a status page status_pages_delete

Permanently deletes a status page together with all its incident posts and subscribers. The public page and its custom domain stop working at once and this cannot be undone. The uptime checks themselves are not deleted. The id comes from status_pages_list. Needs the uptime.manage permission. Returns the deleted page.

Permission: uptime.manage

Example
curl -X DELETE https://privatusanalytics.com/status_pages/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/status_pages.json List status pages status_pages_list

Lists the status pages of a workspace sorted by name. Use query to match part of a name or slug. When the caller is limited to some sites, only pages whose checks are all within those sites are returned. Paginated with page and per_page (up to 100). Each item has the fields described in status_pages_get. Needs the workspace_id from workspaces_list.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/status_pages.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /status_pages/:id.json Get a status page status_pages_get

Returns one status page with its settings. Fields are id (sp_...), workspace_id, name, slug, custom_domain (null when none), description, logo_url, brand_color, published, check_ids (uptime check ids shown on the page), checks (the same checks as {id, name, status}), locked (true when the page is over the number of status pages the plan includes), subscribers (count of confirmed email subscribers) and created_at. Use status_page_posts_list for its incidents and status_page_subscribers_list for the people. The id comes from status_pages_list.

Permission: analytics.read

Example
curl https://privatusanalytics.com/status_pages/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /status_pages/:id.json Change a status page status_pages_update

Changes a status page. Only the inputs you send change, so send just published false to take the page offline or check_ids to replace the shown checks (the whole list is replaced, not merged). The inputs and their limits are the same as in status_pages_create. Send custom_domain as an empty string to remove the custom domain. Only checks with public true can be shown, and a caller limited to some sites can only put checks of those sites on a page. The id comes from status_pages_list. Needs the uptime.manage permission. Returns the updated page.

Permission: uptime.manage

Example
curl -X PATCH https://privatusanalytics.com/status_pages/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Subscriptions

DELETE /sites/:site_id/subscriptions/connections/:provider.json Disconnect a payment provider and delete its key and subscription data subscriptions_disconnect

Disconnects a payment provider (stripe, paddle, lemon_squeezy, chargebee or polar) from a site and permanently deletes the stored API key and every subscription row read from it. This cannot be undone, and MRR, churn and cohort numbers drop that provider's subscriptions immediately. Fails if that provider is not connected. Returns the removed connection. Needs sites.manage.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/subscriptions/connections/:provider.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/subscriptions/sync.json Read subscriptions from a payment provider now subscriptions_sync

Queues an immediate sync of subscriptions from one connected provider (stripe, paddle, lemon_squeezy, chargebee or polar) instead of waiting for the regular schedule. It returns right away (HTTP 202) with the connection and the sync runs in the background. Poll subscriptions_report or check last_synced_at and last_error on the connection. Reports are cached for up to 15 minutes. Fails if the provider is not connected. Needs sites.manage.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/subscriptions/sync.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/subscriptions/cohorts.json Churn cohort grid: churn or retention by month of first payment subscriptions_cohorts

The churn cohort grid for the site's subscriptions, one row per month of first payment. months is 6, 12 (default) or 24 cohorts. plan keeps only the subscriptions of one plan (see plans in subscriptions_report). metric is subscriptions (count) or mrr (money). view is churn (the share that had ended by that point, default) or retention (the share still running). Returns metric, view, months, plan (the filter, or null), currency and cohorts, each with month (YYYY-MM), size (subscriptions, or MRR in minor units of the site currency) and cells, where each cell has offset (months since the cohort month, 0 is the first), retained (same unit as size) and percent (0 to 100, null for an empty cohort). Trials that never paid are in no cohort. For headline MRR, ARR and churn use subscriptions_report. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/subscriptions/cohorts.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/subscriptions.json Subscription metrics: MRR, ARR, MRR movements, churn, revenue retention, trials, plans and a forecast subscriptions_report

Subscription metrics for the site, read from its connected Stripe, Paddle, Lemon Squeezy, Chargebee or Polar account. months is 6, 12 (default) or 24. plan keeps only the subscriptions of one plan, named as plans lists it. Money fields are integers in minor units of the site currency (the response currency), with other currencies converted at today's rate. The result has currency, connected (false when no provider is connected), plan (the plan filter, or null), connections (one per connected provider with provider, account_name, sandbox, status, last_error, last_synced_at and created_at, never a key), skipped_currencies (currencies with no rate, whose money is not counted), totals, series, forecast and plans. totals has mrr_minor, arr_minor, previous_mrr_minor (MRR 30 days ago), active_subscriptions, arpa_minor (null with no subscriptions), trialing, trial_conversion_rate (percent of trials that ended in the last 90 days and paid, null when none ended), and for the last 30 days new_subscriptions, new_mrr_minor, expansion_mrr_minor, contraction_mrr_minor, churned_subscriptions, churned_mrr_minor, net_new_mrr_minor, quick_ratio (new plus expansion MRR for each unit of churned plus contraction MRR, null when nothing was lost), subscription_churn_rate and mrr_churn_rate (percent, null with no base). It also has net_revenue_retention and gross_revenue_retention (percent of what the subscriptions paying 12 months ago paid then that they pay now, with and without upgrades, null with no base) and forecast_mrr_minor (MRR in 12 months, null with no forecast). series has one row per month (month as YYYY-MM) with mrr_minor, active_subscriptions, the same movement fields and churn_rate. forecast has one row per month for the next 12 months (month, mrr_minor), projected from the last three full months, and is empty without three full months that started with MRR. plans lists up to 50 plans by MRR with plan (null for subscriptions the provider gave no plan name), mrr_minor, active_subscriptions, churned_subscriptions and subscription_churn_rate (last 30 days), always for every plan. A subscription counts from its first payment. Upgrades and downgrades are known from the first sync on, and before that past months use the price a subscription had when it was first read. For retention by month of first payment use subscriptions_cohorts. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/subscriptions.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Surveys

POST /sites/:site_id/surveys.json Create a survey surveys_create

Creates a cookieless on-site survey for a site. It collects nothing until its status is active, so create it as draft first or pass status active to start at once. Required are name (internal, visitors never see it, max 120 characters) and questions (1 to 5 rows in order). Each question is an object with key (optional, a stable id of lowercase letters, digits and underscores, generated when omitted), kind (text, choice, multi, rating, nps or yes_no), prompt (required, max 200 characters), required (boolean), choices (a list of up to 10 options, only for choice and multi) and scale (3, 5, 7 or 10, only for rating, default 5). NPS always uses 0 to 10. Targeting is trigger_kind (immediate, delay, scroll, exit or manual) with delay_seconds (0 to 600, default 10, only for delay) and scroll_percent (1 to 100, default 50, only for scroll), paths (rows of match_type exact, prefix or glob and pattern, empty means every page), devices (desktop, mobile, tablet, empty means all), position (bottom_right, bottom_left or center), sample_rate (1 to 100 percent of visitors), thank_you (max 200 characters), starts_on and ends_on (YYYY-MM-DD). Surveys cannot ask for an email address and written answers are scrubbed of personal data. The Free plan allows 1 survey per workspace and a limited number of responses a month, paid plans are unlimited. Needs the content.write permission. Returns the survey with id (svy_...). Read answers with surveys_get.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/surveys.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/surveys/:id.json Delete a survey with its responses surveys_delete

Permanently deletes a survey together with all its responses and answers. This cannot be undone and the results are lost. Use surveys_pause instead if you only want to stop collecting. The id comes from surveys_list and the site_id is required. Needs the content.write permission. Returns the deleted survey.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/surveys/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/surveys.json List a site's surveys with their response counts surveys_list

Lists the surveys of a site, newest first, with their settings and response counts. Filter with status (draft, active, paused or all, default all). Paginated with page and per_page (default 25, up to 100). Each survey has id (svy_...), name, status, questions, targeting settings, starts_on, ends_on, created_at, updated_at and stats (responses, completed and this_month counts). Use surveys_get for the answers and results of one survey.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/surveys.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/surveys/:id/pause.json Stop collecting answers and keep the ones already given surveys_pause

Pauses a survey so it stops being offered to visitors at once. Responses already collected are kept and shown in surveys_get, and surveys_start resumes it. Use it instead of surveys_delete when you only want to stop collecting. The id comes from surveys_list and the site_id is required. Needs the content.write permission. Returns the survey with status paused.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/surveys/:id/pause.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/surveys/:id.json Survey results for a period, broken down per question surveys_get

Returns the results report of one survey over a period, not just its settings (use surveys_list for settings and counts). Choose the period preset (default 30d) or period custom with from and to. The response has survey (its settings), period (from and to as ISO 8601 times), responses (people who answered at least one question), completed (people who answered the last question), questions (one entry per question with key, kind, prompt and answers count, plus detail by kind) and pages, devices and countries (up to 10 rows each of value and count). Detail by kind is texts (up to 100 newest written answers with text and at) for text, choices (value, count, share as percent with one decimal, offered) for choice, multi and yes_no, average (null with no answers), scale and distribution for rating, and score (NPS from -100 to 100, null with no answers), promoters (9 and 10), passives (7 and 8), detractors (0 to 6) and distribution for nps. A text question also has themes (themes as label, count, sentiment and example, plus answers and generated_at) when surveys_themes already made them for its current answers and the plan includes them. This call never generates them. Use surveys_themes to summarize written answers.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/surveys/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/surveys/:id/start.json Start collecting answers surveys_start

Sets a survey to active so visitors can be offered it. It only appears when the current date is within starts_on and ends_on (when set), the page matches its paths and devices, and the plan's monthly response allowance is not used up. Use it for a draft or a paused survey. The id comes from surveys_list and the site_id is required. Needs the content.write permission. Returns the survey with status active. Stop it again with surveys_pause.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/surveys/:id/start.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/surveys/:id/themes.json Group the free-text answers to one question into themes (Business plan) surveys_themes

Uses AI to group the written answers to one free-text question into themes. Business plan only. Choose it after surveys_get shows a text question with enough answers, and pass question_key (the key of a question of kind text from surveys_get). It needs at least 5 written answers and uses the 300 most recent. The result lists up to 12 themes, each with label, count, sentiment (positive, neutral or negative) and example (a representative answer, may be null), plus answers (how many answers were summarized) and generated_at. Each new generation uses one question from the workspace's monthly AI allowance, which is given back if it fails. The result is cached against the answers, so calling again with no new answers costs nothing. It fails with a validation error when the plan does not include it, the allowance is used up, the AI is not configured or there are too few answers.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/surveys/:id/themes.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /sites/:site_id/surveys/:id.json Update a survey surveys_update

Changes a survey. Only the inputs you send change. Inputs and limits are the same as in surveys_create, with status draft, active or paused. When you send questions the whole list is replaced, so include every question you want to keep and keep each existing key to keep its collected answers (rewording a prompt with the same key does not split the results). Questions you leave out stop appearing in results. Changes to targeting take effect for visitors right away. The id comes from surveys_list and the site_id is required. Needs the content.write permission. Returns the updated survey.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/surveys/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial analytics

GET /sites/:site_id/testimonials/analytics/breakdown.json Split one testimonial metric by form, request, widget, wall, testimonial, channel, page, country or source testimonial_stats_breakdown

Returns rows, largest first, each with key, label, value and share (a percent of the total), or numerator and denominator for a rate. A row of a record (form, request, widget, wall, testimonial) carries its id. Each dimension takes only some metrics: form and channel take the form metrics, request the request metrics, widget and wall their own views, clicks, click rate and video_plays, testimonial takes testimonial_views and video_plays, page and country take the view and click metrics, and source takes new_testimonials. A pair that does not fit responds with 422 and names the metrics that do.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/analytics/breakdown.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/analytics/export.json Queue a file of one section of the testimonial analytics testimonial_stats_export

Creates an export of kind testimonial_stats for the period given and returns it queued. Poll exports_get until status is complete, then exports_download. section picks the rows: days (one row per day, week or month with every metric), forms, channels, requests, widgets, walls, pages, testimonials, sources or countries. The file is kept for 7 days. Responds with 422 in_progress when too many exports are already queued.

Permission: analytics.export

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/analytics/export.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/analytics.json Read the testimonial analytics of a site in one call testimonial_stats_overview

Returns everything the Analytics tab under Testimonials shows for a period: cards (form_views, submissions, completion_rate, answer_rate, widget_views, wall_views, each with previous and change when compare is set), chart (one metric over time), a row per form, request, widget and wall, channels (how forms were opened), pages (where widgets and walls were seen), top_testimonials, new_testimonials (by source and by current status) and countries. Rates are percents from 0 to 100 and are null with nothing to divide by. blank is true while the site has never counted anything, and recorded lists the metrics it has ever counted. These are daily totals of the testimonials feature, not web analytics: use stats_overview for visitors and pageviews.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/analytics.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/analytics/timeseries.json Read one testimonial metric per day, week or month testimonial_stats_timeseries

Returns points with date, to, value and, when compare is set, previous and previous_date, plus total, previous_total and change. A rate (completion_rate, answer_rate, widget_click_rate, wall_click_rate) is worked out from the totals of each bucket and is null for a bucket with nothing to divide by. new_testimonials is counted from the testimonials themselves by the day each was added.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/analytics/timeseries.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Testimonial brand kit

DELETE /sites/:site_id/testimonials/settings/brand_kit.json Return the brand kit of a site's testimonials to the Privatus Analytics defaults testimonial_brand_kit_reset

Clears every color, font, the radius, the rating style, the logos and the custom CSS in one step, and every form, widget and wall changes with it. It cannot be undone, so an assistant is sent to the Brand kit page instead of running it.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/settings/brand_kit.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/settings/brand_kit.json Get the brand kit of a site's testimonials, with every color resolved for light and dark testimonial_brand_kit_get

Returns the colors, fonts, corner radius, rating style, logos, badge switch and custom CSS that every form, widget and wall of the site is drawn with. A setting that was never changed comes back with the Privatus Analytics default.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/settings/brand_kit.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/settings/brand_kit.json Change the brand kit of a site's testimonials testimonial_brand_kit_update

Only the inputs you send are changed. Send an empty string to return one setting to the Privatus Analytics default. Custom CSS is checked when it is saved, and anything that is not allowed is refused with the line it is on. Custom CSS needs the Business plan and hiding the badge needs the Business plan. Without it the server responds with 402 plan_limit.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/settings/brand_kit.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial collections

POST /sites/:site_id/testimonials/collections/:id/items.json Add testimonials to a manual collection testimonial_collections_add_items

Up to 100 at once. They join at the bottom, or at the top with position top, in the order given. The response has meta.undo_id, which testimonials_bulk_undo takes for 7 days. A smart collection takes nothing by hand.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/collections/:id/items.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/collections.json Create a collection, manual or smart testimonial_collections_create

kind manual is put together by hand, and testimonial_ids starts it filled. kind smart is kept current by conditions: rows of field, operator and value joined by match. The response carries the count and the rules in plain English, so one call turns a sentence into a living collection. Try the rules first with testimonial_collections_preview. The kind never changes afterward.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/collections.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/collections/:id.json Move a collection to the trash for 30 days testimonial_collections_destroy

Its testimonials are not touched. A widget or wall it feeds shows nothing from it until it is restored. Restore it with testimonial_trash_restore. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/collections/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/collections.json List the collections of a site testimonial_collections_list

Each collection comes with how many testimonials it holds right now, how many of those are approved, its rules in plain English when it is a smart one, and the widgets and walls it feeds. Collections in the trash are left out.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/collections.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/collections/preview.json Try a set of rules without saving anything testimonial_collections_preview

Responds with how many testimonials match right now, the rules in plain English and five examples. Use it before testimonial_collections_create or testimonial_tags_update. An invalid row is a 422 that names the row.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/collections/preview.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
DELETE /sites/:site_id/testimonials/collections/:id/items.json Take testimonials out of a manual collection testimonial_collections_remove_items

Up to 100 at once. The testimonials themselves are not changed. The response has meta.undo_id, and an undo puts each one back in its place.

Permission: content.write

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/collections/:id/items.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/collections/:id/reorder.json Move one testimonial to another place in a manual collection testimonial_collections_reorder

Name the place with exactly one of position (counted from 1), before_id or after_id. The order is the order served to every widget and wall the collection feeds.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/collections/:id/reorder.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/collections/:id.json Get one collection with its counts, its rules and what it feeds testimonial_collections_get

Its testimonials are testimonials_list with collection_id.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/collections/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/collections/:id.json Change a collection's name, description, rules or served order testimonial_collections_update

Only what is sent changes. Sending conditions replaces every rule of a smart collection. The kind is fixed.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/collections/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial companies

GET /sites/:site_id/testimonials/companies.json List the companies of a site with their people, testimonials and average rating testimonial_companies_list

One row per company, grouped from the company name and website of the site's people. A person with no company is in none. query matches part of a company name or of its website host. sort is name (the default), people_desc, testimonials_desc or received_desc.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/companies.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/companies/:id/merge.json Merge a company into another one, for two spellings of one company testimonial_companies_merge

Every person of the company in the path takes the name and URL of the company named by into.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/companies/:id/merge.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/companies/:id.json Get one company with its people testimonial_companies_get

The id is the company's key, a website host or a normalized name, as testimonial_companies_list returns it. Its testimonials are testimonials_list with company.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/companies/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/companies/:id.json Correct a company's name and URL for all of its people at once testimonial_companies_update

The id of a company comes from its URL or name, so the response may carry a new id.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/companies/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial custom domain requests

POST /sites/:site_id/testimonials/settings/custom_domain.json Ask for a custom domain on a wall or on forms testimonial_custom_domain_requests_create

Custom domains for testimonials are set up by the Privatus Analytics team by hand. This stores the request and tells the team, who email the DNS record to add. The hostname does not work until the request's status is active. Business and up: other plans get 402 plan_limit.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/settings/custom_domain.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/settings/custom_domain.json Withdraw a custom domain request, by its hostname testimonial_custom_domain_requests_delete

Only a request the team has not started on (status requested) can be withdrawn. One that is in progress responds with wrong_state. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/settings/custom_domain.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/settings/custom_domain.json List the custom domain requests of a site testimonial_custom_domain_requests_list

Every request for a hostname on a wall or on forms, newest first, with its status. The hostname works only once status is active. Works on every plan, so earlier requests can still be read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/settings/custom_domain.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Testimonial custom properties

POST /sites/:site_id/testimonials/settings/properties.json Add a custom property to a site testimonial_properties_create

A site holds up to 20 active properties. The key is fixed once made, because form links, rules and the API use it. Leave it out and it is suggested from the label.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/settings/properties.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/settings/properties/:id.json Delete a custom property and its values for good testimonial_properties_destroy

This cannot be undone. It removes the property and its value from every testimonial. confirm is the key. A property used by a smart collection or an auto-tag rule cannot be deleted while they use it. Archive is the everyday way to retire one.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/settings/properties/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/settings/properties.json List the custom properties of a site, in display order testimonial_properties_list

Each one says its key, type and choices, how many testimonials hold a value (filled), whether its type can still change, and which smart collections and auto-tag rules use it.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/settings/properties.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/settings/properties/:id.json Change, reorder, archive or bring back a custom property testimonial_properties_update

Only the inputs you send change anything. Renaming a choice renames it on every testimonial that has it. A property used by a smart collection or an auto-tag rule cannot be archived while they use it.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/settings/properties/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial duplicates

POST /sites/:site_id/testimonials/duplicates/dismiss.json Say that a group is not duplicates. It never returns to the queue testimonial_duplicates_dismiss

testimonial_ids must be every copy of one open group of the duplicates queue that is out of the trash, as testimonial_duplicates_list returns them. Nothing is merged or changed on the testimonials, and they are never grouped together again. To split a group of three, merge the two that are the same first. The server responds with 422 when the ids are not exactly one open group.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/duplicates/dismiss.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/duplicates.json List the groups of testimonials that are probably the same one twice testimonial_duplicates_list

Exact matches come first. Each group carries its copies in full and keeper_id, the copy that would be kept. meta.exact_groups is how many groups testimonial_duplicates_merge_exact would merge.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/duplicates.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/:id/merge.json Merge copies of a testimonial into the one in the path testimonials_merge

The keeper keeps its status and verified state, and its words unless body_from names another copy. It gains the tags, manual collections, placements and custom properties it lacks. The copies go to the trash marked as merged, and restoring one undoes the merge for that copy. A copy that arrived this month stops counting as a new testimonial. The response carries meta.undo_id.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/:id/merge.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/duplicates/merge_exact.json Merge every exact match of the queue at once testimonial_duplicates_merge_exact

Send dry_run first for the counts, then confirm_count with the total it gave. Up to 100 groups are merged per call. The response carries meta.undo_id, and one undo takes the whole run back.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/duplicates/merge_exact.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial images

POST /sites/:site_id/testimonials/images/bulk.json Make images for every approved testimonial of a collection testimonial_images_bulk_create

Up to 200 testimonials a run, one image per size each. A text too long for the picture is drawn from its highlight, or else from its opening sentences (excerpted says how many). The response lists the images started, the testimonials left out with the reason, and export_id: one zip of the PNGs appears on the Exports page when they are drawn.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/images/bulk.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/images.json Make images of one approved testimonial, one per size testimonial_images_create

Only an approved testimonial can be drawn, because the PNG gets a public address: any other is refused with not_approved. Each image starts as rendering and is drawn in the background. The response is the list of images started. Poll testimonial_images_get for file_url. A 422 with text_too_long means the words do not fit: use text_mode highlight or excerpt, or a taller size.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/images.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/images/:id.json Delete an image and its file testimonial_images_delete

The PNG is deleted from the media host and its address stops working. An image has no trash. It can be made again from its testimonial with testimonial_images_create. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/images/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/images.json List the images of a site testimonial_images_list

Images made from approved testimonials, newest first, 25 a page. Filter by status, template, size or testimonial_id. Each one carries file_url once it is ready.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/images.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/images/:id.json Get one image, with its state and the address of its PNG testimonial_images_get

status is rendering, ready or failed. file_url is set once it is ready. Poll this after testimonial_images_create until status is no longer rendering.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/images/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/images/:id.json Change how an image is drawn and render it again testimonial_images_update

Send only what changes. With nothing sent it renders the same image again from the testimonial as it is now, which is also how to retry one that failed. A new render has a new file_url, and the old file is deleted.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/images/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial import runs

GET /sites/:site_id/testimonials/imports/sources/:import_source_id/runs.json List the runs of a source, newest first testimonial_import_runs_list

Each run has its number, what started it, its status, the pages read and how many testimonials it found, how many were new, duplicates, failed and kept, with the times it started and finished.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources/:import_source_id/runs.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Testimonial import sources

POST /sites/:site_id/testimonials/imports/sources.json Add an import source from a link and start its first run testimonial_import_sources_create

The platform is detected from the link. What the run finds waits in the review step (testimonial_import_sources_review) and nothing is stored until testimonial_import_sources_keep. Nothing imported counts toward the monthly testimonials. The Free plan fetches 1 import a month from a review site or a page. Past it the response is 402 plan_limit.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/imports/sources/:id.json Delete a source. The testimonials it brought in stay testimonial_import_sources_delete

Deletes the source with its run history, what waits in its review step and its uploaded file. Testimonials already kept from it stay in the site. A person does this on the source's page, so the tool runs nothing and returns that page.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/imports/sources/:id/discard.json Discard found items testimonial_import_sources_discard

Give item_ids (ids from testimonial_import_sources_review) or all true for everything still waiting, never both. A discarded item leaves the review step and what was read about the person is erased. No testimonial is deleted. The server responds with 422 and the code selection or unknown_ids otherwise.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources/:id/discard.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/imports/find.json Find testimonials on the site's own website testimonial_import_sources_find

Reads the site's domain and the pages it links to that look like they hold testimonials, once per site. It is free: it is not one of the Free plan's fetched imports. What it finds waits in the review step of the source it returns.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/find.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/imports.json List the import sources of a site testimonial_import_sources_list

meta carries fetched_imports (used, limit and resets_on for the month, limit null is unlimited) and sync (the pace the plan allows).

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/imports/sources/:id/keep.json Keep found items as testimonials testimonial_import_sources_keep

Give item_ids (up to 100) or all. Every kept item is stored through the same route as any other testimonial, so duplicates are never stored twice. Nothing kept counts toward the monthly testimonials. meta says how many were kept.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources/:id/keep.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/imports/platforms.json List the platforms an import can read testimonial_import_platforms_list

The tested-source registry. A platform is listed only once its import is proven against a real page. Each entry says which link to paste.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports/platforms.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/imports/sources/:id/review.json List what a source found and has not been decided yet testimonial_import_sources_review

meta carries the counts by badge (new, already_here, likely_duplicate).

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources/:id/review.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/imports/sources/:id/run.json Read a source again now testimonial_import_sources_run

A run by hand is a fetched import and counts as one of the month's on the Free plan (402 plan_limit past it). Refused while a run is active (422 in_progress) and after 3 runs of the source in 24 hours on any plan (422 too_many_runs). On an errored source it clears the count of failures.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources/:id/run.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/imports/sources/:id.json Get one import source with its state and counts testimonial_import_sources_get

Returns the platform, the name, the address read, how often it is read again, the state, how many items wait in the review step, how many were kept, the failures in a row and when it last ran and runs next.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/imports/sources/:id.json Change a source's link, name, sync, auto-approve, or pause it testimonial_import_sources_update

A sync above the plan (any on Free, weekly on Pro) is refused with 402 plan_limit.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/imports/sources/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial languages

GET /sites/:site_id/testimonials/settings/languages.json Get the language settings of a site's testimonials testimonial_languages_get

The default language, the languages offered, whether newly approved testimonials are translated automatically, whether the plan includes translation, whether translation is set up, and the month's characters used and left.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/settings/languages.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/settings/languages.json Set the default language, the offered languages and automatic translation testimonial_languages_update

Send only what changes. The default is always among the offered languages, and a language a form is written in cannot be removed (in_use). Switching auto_translate on needs Business or above (402 plan_limit elsewhere): from then on the words of each newly approved testimonial are sent to Google Cloud Translation, and the member is emailed that an assistant changed this.

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/settings/languages.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial media

POST /sites/:site_id/testimonials/media/uploads.json Start an upload, or add a file from a web address testimonial_media_uploads_create

Two ways. To upload, send filename, content_type and byte_size: the response carries upload, with the url to PUT the file to, the headers to send with it and, for a large file, parts that can be sent one by one. Then call testimonial_media_uploads_finish. Or send source_url and the file is downloaded and processed for you: poll testimonial_media_get until its state is ready. Limits: video and audio up to 500 MB and as long as the plan takes (2 minutes on Pro, 10 minutes on Business, none on Free, where the server responds with 402 plan_limit), images up to 10 MB, a logo up to 2 MB.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/media/uploads.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/media/:id.json Delete a file that no testimonial keeps testimonial_media_delete

A file that belongs to a testimonial is deleted with the testimonial, through the trash. The stored files are deleted and their addresses are cleared from the cache.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/media/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/media/uploads/:id/finish.json Say an upload is complete, so the file is checked and processed testimonial_media_uploads_finish

Call it once the file has been sent. The file must be in the upload store and be exactly the size that was promised. Calling it twice is safe.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/media/uploads/:id/finish.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/media/:id/retry.json Try a failed file again testimonial_media_retry

Processes the file again from the upload that was kept. The upload is kept for 24 hours after a failure. After that the server responds with not_retryable and the file has to be uploaded again.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/media/:id/retry.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/media/:id.json Get one file with its state and where to load it testimonial_media_get

The url is the lasting address of the finished file on the media host, and it is null until the state is ready. The address is the same for everyone and is not signed, so only pass on the address of a file whose shareable field is true.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/media/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Testimonial mentions

GET /sites/:site_id/testimonials/mentions.json List the words and phrases that recur across a site's testimonials testimonial_mentions_list

Each term comes with how many testimonials contain it, its share, the average rating of those, and the last 90 days against the 90 before. Sort by rating_asc to read what people complain about, or by rising for what is new. meta.refreshed_at says when the terms were last rebuilt. Pass a term to testimonials_list as mention to read the testimonials.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/mentions.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/mentions/tag.json Tag every testimonial that mentions a word or phrase testimonial_mentions_tag

One bulk action on the whole result, with undo. The term is any word or two-word phrase, matched with stemming the way Mentions are counted. A tag named for the first time is created. Responds with an undo id that testimonials_bulk_undo takes for 7 days. More than 100 run in the background.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/mentions/tag.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /sites/:site_id/testimonials/mentions.json Hide a term that is only noise, or show it again testimonial_mentions_update

A hidden term leaves the Mentions list and the inbox's Mentions facet, and stays hidden when the terms are rebuilt.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/mentions.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial migrations

POST /sites/:site_id/testimonials/imports/migrations/:id/discard.json Discard what a migration read testimonial_migrations_discard

Give item_ids (ids from testimonial_migrations_review) or all true for everything still waiting, never both. A discarded item leaves the review step and what was read about the person is erased. No testimonial is deleted. The server responds with 422 and the code selection or unknown_ids otherwise.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/migrations/:id/discard.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/imports/migrations.json List the migrations of a site testimonial_migrations_list

Newest first. A migration is one export file from another testimonial tool. Each has the tool it came from, its status and how many testimonials were found, kept, duplicates and failed.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports/migrations.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/imports/migrations/:id/keep.json Keep what a migration read as testimonials testimonial_migrations_keep

Give item_ids (up to 100 a call, from testimonial_migrations_review) or all true, never both. Each kept item becomes a testimonial with status pending or approved, and keep_approval true keeps the approval it had in the other tool. With all, likely duplicates are left out unless include_likely_duplicates is true. Nothing kept counts toward the monthly testimonials. What is not kept stays in the review step for 30 days.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/migrations/:id/keep.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/imports/migrations/:id/review.json List what a migration read and has not been decided yet testimonial_migrations_review

Lists the items still waiting for a decision, newest run first. show is new, duplicates or all (the default), and rating keeps only the star ratings listed. Pass the item ids to testimonial_migrations_keep or testimonial_migrations_discard.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports/migrations/:id/review.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/imports/migrations/:id.json Get a migration's progress and counts testimonial_migrations_get

Returns the tool the file came from, the status, how many testimonials were found, kept, duplicates and failed, how many media files it brings and when it started and finished. Poll it while the status says the file is still being read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports/migrations/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Testimonial people

POST /sites/:site_id/testimonials/people/:id/ask_again.json Start a request with this person as its only recipient testimonial_people_ask_again

It uses the form they last used, or the starter form. Nothing is sent until the request is sent. It the server responds with 422 and the code no_email, bounced or unsubscribed when the person cannot be asked, and requests_unavailable while requests are not available.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/people/:id/ask_again.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/people.json Add a person to a site testimonial_people_create

name is required (up to 120 characters). email is never shown publicly: it groups later testimonials under this person and lets the site ask them again, and an address another person of the site already has is refused. avatar_media_id is the id (med_...) of an image already uploaded to the site. Adding a person adds no testimonial.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/people.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/people/:id/permanent.json Delete a person for good, with everything they said, the files and the ask history testimonial_people_delete_permanently

For an erasure request. This cannot be undone. It needs confirm, the person's name or id, and leaves one audit entry that holds neither the name nor the words.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/people/:id/permanent.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/people.json List the people of a site, filtered and sorted testimonial_people_list

Each person comes with their count of testimonials, how many are approved, their average rating and the last ask with its outcome. The email is null unless the caller holds content.write, and has_email still says whether one is on record.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/people.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/people/:id/merge.json Merge other records of the same person into this one testimonial_people_merge

The person in the path stays. Everything the others said and every ask sent to them moves over, and the others are removed. A field this person has blank is filled from the others, oldest first, and use names whose value wins for a field. The response carries meta.undo_id, which testimonial_undo_records_undo takes for 7 days.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/people/:id/merge.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/people/:id.json Get one person with what was asked of them and the people who look like the same person testimonial_people_get

Their testimonials are testimonials_list with person_id. can_ask and cannot_ask_reason say whether testimonial_people_ask_again would go through.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/people/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/people/:id.json Edit a person. Only the fields sent change testimonial_people_update

An empty value clears a field, except the name. An email another person of the site already has is refused. The company page a person is listed on follows the company name and website.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/people/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial purchase connections

DELETE /sites/:site_id/testimonials/settings/purchase_connections/:id.json Disconnect a payment account testimonial_purchase_connections_delete

Deletes the connection and its key. Buyers still waiting for their first email are not asked. The standing request stays as the record of what was sent.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/settings/purchase_connections/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/settings/purchase_connections.json List the purchase connections of a site testimonial_purchase_connections_list

Each with its rule, its state and its standing request. The key is never returned. paused is true on a plan without requests after a purchase: the connection is kept, reads no payments and asks nobody.

Permission: sites.manage

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/settings/purchase_connections.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Testimonial request recipients

POST /sites/:site_id/testimonials/requests/:request_id/recipients.json Add people to a request testimonial_request_recipients_create

Up to 500 in one call. Added to a draft they wait for testimonial_requests_send. Added to a running request they get their first email at once. The response says how many were queued and how many skipped, by reason. Adding to a running request emails people outside the workspace, so the member is told by email.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:request_id/recipients.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/requests/:request_id/recipients.json Remove chosen people from a request testimonial_request_recipients_delete

Only people nothing was sent to (queued or skipped). Takes up to 100 addresses in the body. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/requests/:request_id/recipients.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/requests/:request_id/recipients/import.json Add people to a request from a CSV file testimonial_request_recipients_import

Two calls. Send the file (or its text as csv) to get the columns, their first rows and an upload_token. Then send the mapping from column number to field. Up to 5 MB and 10,000 rows. Adding to a running request emails people outside the workspace, so the member is told by email.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:request_id/recipients/import.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/requests/:request_id/recipients.json List the people of a request testimonial_request_recipients_list

Filter by where a person stands now: status opened is "opened and did not click or answer". The email address is null unless the caller may edit content.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/requests/:request_id/recipients.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/requests/:request_id/recipients/resume.json Resume chosen people of a request testimonial_request_recipients_resume

They continue where they stopped. Takes up to 100 addresses in the body. This emails people outside the workspace, so the member is told by email.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:request_id/recipients/resume.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/requests/:request_id/recipients/stop.json Stop chosen people of a request testimonial_request_recipients_stop

They get no further email. Each keeps their status and gains "stopped". Takes up to 100 addresses in the body.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:request_id/recipients/stop.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial requests

POST /requests/resubscribe/:token.json Confirm receiving testimonial requests again testimonial_request_resubscribes_confirm

The POST behind the button of the confirmation page. It lifts the opt-out, so the address can be asked again.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/requests/resubscribe/:token.json \
  -H "Content-Type: application/json" -d '{…}'
POST /requests/resubscribe.json Ask for the email that lets an address receive requests again testimonial_request_resubscribes_create

Sends a confirmation email to the address itself, when it had unsubscribed. The response is the same whether it had or not.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/requests/resubscribe.json \
  -H "Content-Type: application/json" -d '{…}'
POST /requests/unsubscribe/:token.json Unsubscribe an address from testimonial requests testimonial_request_unsubscribes_create

The POST behind the unsubscribe page's button, and what a mail app's own unsubscribe button calls. The token comes from the link in the email.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/requests/unsubscribe/:token.json \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/requests.json Start a request as a draft testimonial_requests_create

Creates a draft with its email and its recipients. Nothing is sent until testimonial_requests_send. Anything left out starts from a default: the email, one follow-up after 5 days, the site's name as the sender and you as the reply address. Each recipient is stored as queued, or as skipped with the reason.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/requests/:id.json Delete a draft testimonial_requests_delete

Only a request that was never sent can be deleted. A sent request is kept as the record of mail that was sent. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/requests/:id/duplicate.json Copy a request into a new draft testimonial_requests_duplicate

The copy has the same form and email, and no recipients yet.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id/duplicate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/requests.json List the requests of a site testimonial_requests_list

Newest first, each with its email and its funnel counts. Filter by status, form or period.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/requests.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/requests/:id/preview.json See one email of a request as its first recipient gets it testimonial_requests_preview

The sender, the subject and the body with the merge fields filled in. Nothing is sent. step 0 is the first email, 1 to 3 the follow-ups.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id/preview.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/requests/:id/resume.json Resume a stopped request testimonial_requests_resume

Continues where it stopped. A follow-up that came due while stopped is timed again from now, so nothing goes out in a bunch. This emails people outside the workspace, so the member is told by email that an assistant resumed it.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id/resume.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/requests/:id/send.json Send a request, now or at a time testimonial_requests_send

Needs confirm_relationship: true. The queued recipients are checked once more, then the first emails go out (or wait for send_at). Follow-ups go to the people who have not answered. Request emails are sent through the site's own email provider (Settings > Email sending, Business and up), so a site without a working connection gets 422 no_email_connection or email_connection_paused, and another plan gets 402 plan_limit. Every email counts toward the site's monthly cap of request emails (10,000 unless we set another). At the cap nothing is dropped: the people stay queued and sending continues on the 1st. This emails people outside the workspace, so the member is told by email that an assistant sent it.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id/send.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/requests/:id/send_test.json Send one email of a request to your own address testimonial_requests_send_test

Goes to the caller's own address only, through the site's own email provider like the real ones (422 no_email_connection without one). It has no personal link and no open counter, and does not count toward the site's monthly cap.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id/send_test.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/requests/:id.json Get one request with its email, follow-ups and funnel counts testimonial_requests_get

Returns the email, the follow-ups, the status and the funnel counts (sent, opened, clicked, submitted, bounced, unsubscribed) of one request.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/requests/:id/stop.json Stop a request testimonial_requests_stop

Cancels every email not yet sent, at once. Nothing already sent is affected, and testimonial_requests_resume continues where it stopped.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id/stop.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /sites/:site_id/testimonials/requests/:id.json Change a request's name, form or email testimonial_requests_update

Allowed at any time. A change applies to every email not yet sent. Changing a follow-up's delay re-times the follow-ups that are waiting.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/requests/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial saved views

POST /sites/:site_id/testimonials/views.json Save a filter, search text, sort and layout of the inbox under a name testimonial_saved_views_create

A personal view needs only analytics.read. A shared one needs content.write and is refused with the code not_allowed otherwise. A pinned view sits in the sidebar with its count. Open it with testimonials_list and view_id.

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/views.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/views/:id.json Delete a saved view. No testimonial is touched testimonial_saved_views_destroy

A saved view is a stored filter, so only the view goes. A widget or wall fed by the view keeps its settings and has nothing to show until it is given another feed. A personal view is deleted by its owner, and a shared one needs content.write. Otherwise the call is refused with the code not_allowed. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: analytics.read · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/views/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/views.json List the saved views of a site that the caller can open testimonial_saved_views_list

Returns the caller's own views and every shared one, pinned ones first in their order, then by name. scope is all (the default), mine or shared. Pass a view's id as view_id to testimonials_list to read the testimonials it holds.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/views.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/views/:id.json Rename a saved view, change what it stores, pin it or share it testimonial_saved_views_update

Only the inputs sent change: name, the stored search (q), filters, sort, layout, columns, position, pinned and shared. A personal view is changed by its owner. Changing a shared view, and sharing or unsharing any view, needs content.write.

Permission: analytics.read

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/views/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial spreadsheet imports

POST /sites/:site_id/testimonials/imports/spreadsheet.json Upload a spreadsheet (or send CSV text) and read its columns testimonial_spreadsheet_imports_create

Step 1 of a spreadsheet import. Send file (CSV, TSV or XLSX, 10 MB and 5,000 rows at most, headers in the first row) or content (CSV text up to 1 MB). The response has the columns, three sample values of each and a first guess of where each column goes. Nothing is imported yet: continue with testimonial_spreadsheet_imports_update and testimonial_spreadsheet_imports_run. A spreadsheet never counts toward the monthly testimonials or the fetched imports.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/spreadsheet.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/imports/spreadsheet/:id/run.json Read the matched file into the review step testimonial_spreadsheet_imports_run

Rows that can be used wait with a tick box. Rows that cannot are listed with their row number and the reason. Keep them with testimonial_import_sources_keep, using the spreadsheet's id as the source id.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/imports/spreadsheet/:id/run.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/imports/spreadsheet/:id.json Get a spreadsheet's columns, samples, matching, options and counts testimonial_spreadsheet_imports_get

Returns the file's columns with sample values, which field each column goes to, the options (default status, tags, collection, date format, what happens to duplicates) and, once it has run, how many rows wait in the review step and how many were kept.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/imports/spreadsheet/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/imports/spreadsheet/:id.json Save which column goes where, and the options for the file testimonial_spreadsheet_imports_update

Steps 2 and 3 of a spreadsheet import. mapping is an object of column name to field, with an empty value for a column to leave out, and two columns cannot go to one field. The options are default_status (pending or approved), tags, collection_id (a manual collection), default_source_name, date_format (auto, ymd, dmy or mdy) and duplicates (skip or queue). Only what is sent changes, and nothing is imported until testimonial_spreadsheet_imports_run.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/imports/spreadsheet/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial tags

POST /sites/:site_id/testimonials/tags/:id/apply_rules.json Run a tag's rules over the testimonials already stored testimonial_tags_apply_rules

dry_run true (the default) changes nothing and responds with the counts: how many would be tagged, of how many, and how many already have the tag. dry_run false tags them as one bulk action and responds with undo_id, which testimonials_bulk_undo takes for 7 days. More than 100 run in the background and the response has state running.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/tags/:id/apply_rules.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/tags.json Create a tag, with auto-tag rules if you send them testimonial_tags_create

A rule is { match, conditions }. A testimonial that satisfies any one rule gets the tag at the moment it arrives. Rules only ever add a tag. To tag what is already stored, call testimonial_tags_apply_rules next.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/tags.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/tags/:id.json Move a tag to the trash for 30 days testimonial_tags_destroy

It leaves every testimonial, filter and picker. Restoring it with testimonial_trash_restore puts it back on every testimonial that had it. A smart collection whose rules name it matches nothing until then. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/tags/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/tags.json List the tags of a site with their rules testimonial_tags_list

Each tag comes with how many testimonials carry it, its auto-tag rules as structured conditions and in plain English, and when a rule last applied it. Tags in the trash are left out.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/tags.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/tags/:id/merge.json Merge a tag into another one testimonial_tags_merge

Every testimonial that had this tag gets the other, every rule moves to the other, every smart collection, saved view, widget and wall that named this tag names the other, and this tag goes to the trash, empty. Responds with the tag that is kept.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/tags/:id/merge.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /sites/:site_id/testimonials/tags/:id.json Change a tag's name, color or rules testimonial_tags_update

Only what is sent changes. Sending rules replaces every rule of the tag, and an empty list removes them all.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/tags/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial translations

POST /sites/:site_id/testimonials/translations.json Translate testimonials into the languages a site offers testimonial_translations_create

Call testimonial_translations_estimate first. This sends the body, title and highlight of the chosen testimonials to Google Cloud Translation (never a name, an email or a company), and the member is emailed that an assistant started it. The translating happens in the background, and a row that is already stored is never fetched again. At the month's character quota the rest are stored as waiting and continue on the 1st. Responds with 402 plan_limit below Business, and with 422 not_configured while translation is not set up.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/translations.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/translations/:testimonial_id/:language.json Remove one stored translation testimonial_translations_delete

The testimonial is shown in its own words again wherever that language was used. Characters already spent on it are not returned. Works on every plan. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/translations/:testimonial_id/:language.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/translations/estimate.json Count the characters a translation run would send, before running it testimonial_translations_estimate

Say which testimonials one way: testimonial_ids, collection_id or all_approved. The response gives the testimonials, the characters that would be sent, the characters already stored and what is left of the month. It states characters, never a price. Nothing is fetched.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/translations/estimate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/translations.json List the stored translations of a site testimonial_translations_list

One row per testimonial and language, most recently changed first, 25 a page. Filter by language, state, origin or testimonial_id. Works on every plan, so translations made before a downgrade can still be read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/translations.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/translations/:testimonial_id/:language.json Write or correct one translation by hand testimonial_translations_update

The row is marked as edited, and a later run does not overwrite it unless overwrite_edited is sent. Nothing is fetched and nothing counts toward the quota. The original is never changed. Business and up (402 plan_limit elsewhere).

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/translations/:testimonial_id/:language.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial trash

DELETE /sites/:site_id/testimonials/trash/permanent.json Delete up to 100 items in the trash for good testimonial_trash_delete_permanently

This cannot be undone. confirm is the name of the item (the person's name for a testimonial) or its id for one item, and the name of the site for several. Only items in the trash are accepted.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/trash/permanent.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/trash/empty.json Delete everything in the trash for good testimonial_trash_empty

This cannot be undone. confirm is the name of the site. Up to 100 items are deleted at once. A bigger trash is emptied in the background and the response has state running.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/trash/empty.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/trash.json List what is in the trash, one kind at a time testimonial_trash_list

Responds with the items of one kind (testimonial by default) and, in meta.counts, how many of every kind are in the trash. Each item says what it returns to, what showed it, who trashed it and when it leaves.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/trash.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/trash/restore.json Restore up to 100 items from the trash, exactly as they were testimonial_trash_restore

The ids must be of one kind. Restoring testimonials responds with an undo id (data.id and meta.undo_id) that testimonial_undo_records_undo takes for 7 days. A restore never counts as a new testimonial.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/trash/restore.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonial undo list

GET /sites/:site_id/testimonials/settings/undo.json List what was changed in the last 7 days and can be undone testimonial_undo_records_list

Newest first. Each record says what ran, who ran it, how many it changed and skipped and why, and until when it can be undone.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/settings/undo.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/settings/undo/:id.json Read one undo record testimonial_undo_records_get

For a run in the background, state is running and processed says how far it is.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/settings/undo/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/settings/undo/:id/undo.json Undo a bulk action or a single change within 7 days testimonial_undo_records_undo

Puts the earlier value back on everything the run changed. What was changed again since is left alone and counted in left. A merge of people or of duplicates is undone the same way. Over MCP an undo that would delete or move to the trash is not run (the undo of a restore, of a translate run and of a make images run): the tool responds with human_only and the page of the app where a person does it.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/settings/undo/:id/undo.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Testimonials

GET /sites/:site_id/testimonials/overview.json Read the numbers of a site, what needs attention and what happened lately testimonials_overview

stats holds the five numbers of the Overview page: approved testimonials, pending, new this month, average rating and in use. attention lists what needs a decision with its count. activity is the latest 10 events and latest_approved testimonials the three most recently approved.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/overview.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/setup.json Get the setup checklist of a site testimonials_setup_get

Returns the items import, testimonials, approve, ask, widget and teammate, each with done and dismissed. An item is done from what the site holds, never by a click. hidden is true once every item is done or dismissed, which is when the Inbox stops showing the card.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/setup.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/setup.json Dismiss one item of the setup checklist, or the whole card testimonials_setup_update

dismiss is one item key (import, testimonials, approve, ask, widget or teammate) or all for the whole card. Dismissing hides the item and never marks it done, and it cannot be brought back through the API.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/setup.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/:id/approve.json Approve a testimonial so it may be shown testimonials_approve

A testimonial waiting for allowance cannot be approved: the server responds with 422 and the code waiting_for_allowance. The response carries meta.undo_id.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/:id/approve.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/bulk.json Run one action over a selection of testimonials, or over everything a filter matches testimonials_bulk

Send ids (up to 100, run at once, all or none) or filters with confirm_count (the whole result). More than 100 run in the background and the response has state running. Actions: approve, reject, mark_pending, pin, unpin, add_tags, remove_tags, add_to_collection, remove_from_collection, set_property, trash, restore. Each responds with an undo id that testimonials_bulk_undo takes for 7 days. delete_permanently is the one action that cannot be undone: it needs sites.manage and its response says undoable false. Over MCP the two actions that delete, trash and delete_permanently, are not run: the tool responds with human_only and the page of the app where a person does it, and every other action runs. Over the API, action export (with ids or filters, and an optional format of csv, json, ndjson or xlsx) changes nothing: it needs analytics.export and responds with 202 and the export to download.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/bulk.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/bulk/:id/undo.json Undo a bulk action within 7 days testimonials_bulk_undo

Puts the earlier value back on every testimonial the run changed. One changed again since is left alone and counted in left. Over MCP an undo that would delete or move to the trash is not run (the undo of a restore, of a translate run and of a make images run): the tool responds with human_only and the page of the app where a person does it.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/bulk/:id/undo.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials.json Add a testimonial by hand or through the API testimonials_create

Stored through the one intake every route uses. It counts toward the plan's monthly allowance of new testimonials, and at the allowance the server responds with 422 and the code allowance_reached (only a customer's form submission is held as waiting instead). The same person saying the same words again is refused with the code duplicate unless allow_duplicate is true. The same external_id again returns the one already stored.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/:id/permanent.json Delete a testimonial permanently, with its history, consent record and files testimonials_delete

This cannot be undone. It works on a testimonial in the trash or out of it, needs confirm and the sites.manage permission. Over MCP the tool deletes nothing and responds with the page where a person does it.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/:id/permanent.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/:id.json Move a testimonial to the trash for 30 days testimonials_trash

The testimonial keeps its state, tags, collections and files, is shown nowhere and is deleted permanently after 30 days. testimonials_restore puts it back exactly as it was. The response carries meta.undo_id. Trashing one already in the trash changes nothing. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/facets.json Count the testimonials behind every filter value, for the same inputs as the list testimonials_facets

The shape of the library without reading any testimonial. Each facet counts its values with its own choice left out and every other filter applied. meta carries the total and the filter in words.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/facets.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/:id/highlight.json Mark the best sentence of a testimonial, or clear it testimonials_highlight

highlight must be an exact part of the testimonial's words, or the server responds with 422. An empty highlight clears it. Each change is a version in the history.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/:id/highlight.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/:id/history.json List every version of a testimonial's words, title, rating and highlight testimonials_history

Newest first. Version 1 is the testimonial as it was received and has no author. Each later version has the member who made it, when, the note they left and, for a restore, the version it put back. Paginated with page and per_page.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/:id/history.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials.json List the testimonials of a site, filtered and sorted testimonials_list

Three ways to say which, and they add up: the facet inputs (status, tag_id, has_video and the rest), q (the search box with its operators, for example rating:5 has:video tag:onboarding "saved us") and conditions with match (structured rows). Send facets as true for the live count behind every facet value in meta.facets. Nothing in the trash is listed unless trashed is true, which lists only the trash.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/:id/pend.json Put a testimonial back to pending testimonials_pend

Takes an approved or rejected testimonial back to pending, so it is shown nowhere until it is decided again. The response carries meta.undo_id. A testimonial in the trash is refused with the code in_trash.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/:id/pend.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/:id/pin.json Pin a testimonial so it is shown first, or unpin it testimonials_pin

A pinned testimonial is shown first wherever it is displayed. Send pinned false to unpin. The response carries meta.undo_id when something changed.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/:id/pin.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/:id/reject.json Reject a testimonial. It is kept and shown nowhere testimonials_reject

The testimonial keeps everything it has and is shown nowhere. Rejecting one that is already rejected changes nothing. The response carries meta.undo_id, which testimonials_bulk_undo takes for 7 days. A testimonial in the trash is refused with the code in_trash.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/:id/reject.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/:id/restore.json Restore a testimonial from the trash, exactly as it was testimonials_restore

Returns a trashed testimonial to its state, tags, collections and the places that showed it. It never counts as a new testimonial. One that is not in the trash is refused with the code not_in_trash.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/:id/restore.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/:id/restore_version.json Put back the words, title, rating and highlight of an earlier version testimonials_restore_version

Nothing is removed from the history. The restore is a new version that says which one it came from. version 1 returns the testimonial to the words as received. An unknown version number is a 422.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/:id/restore_version.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/:id.json Get one testimonial with its person, files, consent record, tags and where it is used testimonials_get

Returns one testimonial of the site by its id (tm_...), in the trash or not. person.email is null unless the caller holds content.write, and has_email says whether one is on record. media lists the files with their processing state. used_in lists every widget, wall and image that shows it.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/showable.json List the testimonials the public may see, as the public may see them testimonials_showable

The newest approved testimonials of a site that may be shown in public, for a display of your own outside the widgets and walls. Each one carries only what its consent record allows: a person whose name may not be shown reads A customer, and a photo or a recording is left out unless it was allowed. A pending, rejected or trashed testimonial, or one waiting for the allowance, is never listed. There is no email in the response.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/showable.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/triage.json Get the next testimonial of a triage queue testimonials_triage

With no filter the queue is Pending, not waiting for allowance, oldest received first. With the inbox filters it is that result in its sort. Send after with the id just handled to get the one that follows. data is null when the queue is done. meta carries remaining and total.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/triage.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/:id.json Edit a testimonial. Changes to the words, title, rating and highlight are kept in its history testimonials_update

Only the inputs sent are changed. A change to body, title, rating or highlight adds a version to the history with edit_note, and never changes the verified mark. rating null clears it. highlight must be an exact part of the words, and an empty one clears it. properties changes only the keys sent. add_tags and remove_tags take names or ids. State, pin and trash have their own tools. A testimonial in the trash cannot be edited.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Tracking

POST /sites/:site_id/tracking/ingest_key.json Create or rotate the crawler log key (shown once) tracking_ingest_key_rotate

Creates a new secret crawler log key (it starts with pik_) for a site and replaces any existing key. The key authenticates uploads of server access logs for the AI crawlers report (POST /api/logs). Use it both to create the first key and to rotate. The new key works at once and the old one stops working at once, so deploy the new key right away to avoid 401s on log uploads. The full key is returned only in this response, in the key field, so store it in a secret store. Afterwards only a short prefix is kept and shown (prefix here, ingest_key_prefix on the site). Returns site_id, key and prefix. Requires sites.manage. Use tracking_ingest_key_revoke to remove the key without replacing it.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/tracking/ingest_key.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/tracking/ingest_key.json Revoke the crawler log key tracking_ingest_key_revoke

Removes a site's crawler log key. From then on every log upload sent with that key is rejected with 401 until you create a new one with tracking_ingest_key_rotate. Browser tracking with the public site id is not affected. The key cannot be restored. Requires sites.manage. Returns the site object (see sites_get), where ingest_key_prefix is now null.

Permission: sites.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/tracking/ingest_key.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/tracking/verify.json Check the tracker is installed on the site (script tag, site id, CSP and referrer policy) tracking_verify

Fetches the site's page and checks that the Privatus Analytics tracking script is installed correctly, and marks the site as verified when the script with this site's id is found. By default it requests the homepage (https://domain/) and url can name another page, but only on the site's own domain, extra domains or their subdomains. The result (under data) has url, status (HTTP status), script_found, site_ids (every data-site id found on the page), installed (true when this site's id is among them), csp and csp_ok (whether the page's Content-Security-Policy lets the tracker load and send), referrer_policy and referrer_ok (a no-referrer policy means every visit shows as direct), and checked_at. It fetches over the public internet, reads at most 2 MB, and is rate limited per workspace and per host, so it responds with 429 when called too often. If the page is unreachable it returns a validation error. Use sites_setup_get to see whether events have arrived. Requires sites.manage.

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/tracking/verify.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/tracking/snippet.json Build the tracking snippet for chosen options tracking_snippet

Builds the HTML script tag to install tracking on a site and returns it with the site id as data.site_id and data.snippet. Choose this over sites_setup_get when you need to customize it, and use tracking_verify afterwards to check the live page. By default every tracker module is loaded and pageviews are automatic. modules narrows it to a subset of engage (time and scroll), auto (outbound links, downloads, forms, 404s), vitals (Web Vitals), clicks (click maps) and ask (on-site surveys). manual true disables the automatic pageview so you send them yourself. spa sets single-page-app navigation tracking to auto, history, hash or off. domains limits tracking to those hostnames, exclude lists path globs to skip, query_params lists extra query parameters to keep and mask lists client-side path masks written as /u/*->/u/:id. Nothing is saved, the snippet is generated from the inputs each time. Requires analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/tracking/snippet.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Traffic rules

POST /sites/:site_id/traffic_rules.json Add a traffic rule traffic_rules_create

Adds a pattern to one of the site's block or allow lists. list is one of hostnames, paths, referrers, countries, ip_ranges, user_agents or events. pattern uses * as a wildcard and matching is case-insensitive, for example /admin/*, a hostname, a referrer host such as spam.example, a two-letter country code (stored uppercase), a browser name with major version as the user agent is reduced (for example Chrome 120), or a custom event name. ip_ranges take an IPv4 or IPv6 address or CIDR (203.0.113.0/24), which must be valid. A pattern can exist once per list, and a site can have up to 500 rules. Whether the list blocks or allows matches depends on its mode, which defaults to block and is changed with sites_update. Matching hits are dropped at collection and not stored, and the rule applies within minutes to new hits only. Needs sites.manage. Needs the Pro plan or above (402 plan_limit otherwise).

Permission: sites.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/traffic_rules.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/traffic_rules/:id.json Delete a traffic rule traffic_rules_delete

Deletes a traffic rule, found by its id from traffic_rules_list. Hits it was dropping are counted again from then on, and hits already dropped are gone for good. Note that deleting the last pattern of an allow-mode list makes that list stop filtering. Returns the deleted rule. Needs sites.manage.

Permission: sites.manage

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/traffic_rules/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/traffic_rules.json List traffic rules traffic_rules_list

Lists the site's traffic rules, which are block or allow lists applied at collection time to decide which hits count. Each rule has id (a UUID for update and delete), list (hostnames, paths, referrers, countries, ip_ranges, user_agents or events), pattern, label (optional note), mode (block or allow, the mode of that whole list) and created_at. In block mode a hit matching any pattern of the list is dropped, in allow mode a hit matching none is dropped, and an empty list filters nothing. Filter with list. The mode of a list is not set here, it is set with sites_update using traffic_rule_modes. Use traffic_rules_test to check a sample hit. Needs analytics.read.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/traffic_rules.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/traffic_rules/test.json Test whether a hit would be counted with the site's current rules traffic_rules_test

Dry run that tells you whether a sample hit would be counted under the site's current traffic rules. Nothing is stored. Send any of url (a full page URL, its host and path are checked), referrer (URL or hostname), country (two-letter code), ip (checked against ip_ranges, never stored), user_agent (a full User-Agent string, reduced to browser and major version) and event (a custom event name). Values you leave out are not checked. The result has counted (true if the hit would be recorded), blocked_by (the list that dropped it, or null), mode (block or allow for that list, or null) and values (the normalized values that were checked). Rules are read live so a new rule shows at once. Needs analytics.read. Needs the Pro plan or above (402 plan_limit otherwise).

Permission: analytics.read

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/traffic_rules/test.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /sites/:site_id/traffic_rules/:id.json Change a traffic rule traffic_rules_update

Changes the pattern or label of a traffic rule, found by its id from traffic_rules_list. Only inputs you send change. The list cannot be changed, delete and recreate the rule to move it to another list. The pattern must stay valid for its list (a CIDR for ip_ranges) and unique within it. Send an empty label to clear it. Applies to new hits only. Needs sites.manage. Needs the Pro plan or above (402 plan_limit otherwise).

Permission: sites.manage

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/traffic_rules/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Uptime

POST /sites/:site_id/uptime.json Add an uptime check for a site uptime_checks_create

Adds an uptime check tied to one site. Use workspace_uptime_checks_create for a check that belongs to no site. kind decides the target. http checks the status code of url, keyword checks that the response body of url contains keyword, tcp connects to host and port, and dns checks that host resolves. Fields for other kinds are ignored. url defaults to the site's home page for http checks. expected_status (100 to 599, default 200), http_method (GET or HEAD) and timeout_seconds (1 to 30, default 20) apply to http and keyword checks. interval_seconds is the time between probes, it must be at least the plan minimum (Free 300, Pro 60, Business 30) and defaults to 300 or the minimum if that is higher. public true lets the check appear on a status page. Private and internal addresses are refused, and the plan limits the number of checks per workspace (Free 3, Pro 20, Business 100). The first probe runs within moments, status starts as pending. Needs the uptime.manage permission. Returns the check with id (upc_...).

Permission: uptime.manage

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/uptime.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /uptime/checks/:id.json Delete an uptime check and its history uptime_checks_delete

Permanently deletes an uptime check with its results and incident history, and removes it from any status page that shows it. This cannot be undone. Use uptime_checks_pause to stop probing but keep history. The id comes from uptime_checks_list. Needs the uptime.manage permission. Returns the deleted check.

Permission: uptime.manage

Example
curl -X DELETE https://privatusanalytics.com/uptime/checks/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/uptime.json List a site's uptime checks uptime_checks_list

Lists the uptime checks of one site, including workspace wide checks that probe the site's domain or a subdomain of it. Sorted by name. Use workspace_uptime_checks_list for every check in the workspace. Filter with status (up, down, pending or paused) or query (part of name, url or host). Paginated with page and per_page (up to 100). Each check has id (upc_...), workspace_id, site_id (null for workspace wide checks), name, kind, url, host, port, keyword, expected_status, http_method, interval_seconds, timeout_seconds, status (up, down, pending or paused), enabled, public, last_checked_at, next_check_at, last_response_ms (milliseconds), ssl_expires_at, ssl_issuer, domain_expires_at, created_at and uptime (percentages for the last 24h, 7d and 30d, null until the first probe has run). Use uptime_checks_get for response times and incidents.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/uptime.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /uptime/checks/:id/pause.json Pause an uptime check uptime_checks_pause

Pauses an uptime check so it is no longer probed. Any open incident is closed at once and no down or up alerts are sent while it is paused. History is kept and its status shows as paused. Resume it with uptime_checks_resume. The id comes from uptime_checks_list. Needs the uptime.manage permission. Returns the check.

Permission: uptime.manage

Example
curl -X POST https://privatusanalytics.com/uptime/checks/:id/pause.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /uptime/checks/:id/resume.json Resume a paused uptime check uptime_checks_resume

Resumes a paused uptime check. It is probed on the next scheduler tick and its status is pending until the first result comes in. The id comes from uptime_checks_list. Needs the uptime.manage permission. Returns the check.

Permission: uptime.manage

Example
curl -X POST https://privatusanalytics.com/uptime/checks/:id/resume.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /uptime/checks/:id.json Get an uptime check: uptime 24h/7d/30d, response times and incidents uptime_checks_get

Returns one uptime check with its history, not just its settings. Choose range (24h default, 7d or 30d) for the response time chart. The response has check (settings and live state, see uptime_checks_list), uptime (percent for 24h, 7d and 30d, null until the first probe has run), range, response_times (unit hour or day, labels as ISO 8601 times, and series of average response time in milliseconds per location with null for gaps) and incidents (the 50 most recent with started_at, resolved_at null while still open, duration_seconds, cause and failed_locations). Only successful probes count toward response times. The id comes from uptime_checks_list.

Permission: analytics.read

Example
curl https://privatusanalytics.com/uptime/checks/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /uptime/checks/:id.json Change an uptime check uptime_checks_update

Changes an uptime check. Only the inputs you send change. Rules and limits are the same as in uptime_checks_create (the interval must be at least the plan minimum, private addresses are refused). Changing kind clears the target fields that no longer apply. Changing interval_seconds schedules the next probe immediately. The id comes from uptime_checks_list. Needs the uptime.manage permission. Returns the updated check.

Permission: uptime.manage

Example
curl -X PATCH https://privatusanalytics.com/uptime/checks/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /uptime/probes.json Probe datacenter, User-Agent and egress IP ranges (for allowlisting our uptime probes) uptime_probes_list

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl https://privatusanalytics.com/uptime/probes.json
POST /uptime/probes/results.json Submit results from an external probe agent (Bearer uptime probe token) uptime_probes_results

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/uptime/probes/results.json \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/uptime.json Add a workspace uptime check (not tied to a site) workspace_uptime_checks_create

Adds an uptime check that belongs to the whole workspace instead of one site. Use uptime_checks_create to tie a check to a site. Inputs, plan limits and rules are the same, see uptime_checks_create. kind is http, keyword, tcp or dns and decides which of url, keyword, host and port apply. Because there is no site, an http check needs an explicit url. Callers limited to some sites cannot create workspace wide checks. Needs the workspace_id from workspaces_list and the uptime.manage permission. Returns the check with id (upc_...) and site_id null.

Permission: uptime.manage

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/uptime.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/uptime.json List all uptime checks in a workspace workspace_uptime_checks_list

Lists every uptime check of a workspace, including checks tied to no site, sorted by name. A caller limited to some sites only sees checks of those sites. Use uptime_checks_list for the checks of one site. Filter with status (up, down, pending or paused) or query (part of name, url or host). Paginated with page and per_page (up to 100). Each item has the fields described in uptime_checks_list. Needs the workspace_id from workspaces_list.

Permission: analytics.read

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/uptime.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"

Walls of Love

POST /sites/:site_id/testimonials/walls.json Create a Wall of Love testimonial_walls_create

A hosted page at https://privatus.page/w/<slug>. Anything left out starts from a default: the title Wall of Love, the masonry layout, the slug from the name.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/walls.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/walls/:id.json Move a wall to the trash testimonial_walls_trash

Its page responds that it has been taken down until it is restored, and an embedded copy renders nothing. Restoring brings it back at the same address. It is deleted for good after 30 days. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/walls/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/walls/:id/duplicate.json Copy a wall under a new name and address testimonial_walls_duplicate

Makes a copy with a new id, the name ending in "copy" and a slug of its own, so it has its own address. The original and its address are not changed.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/walls/:id/duplicate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/walls/:id/embed.json Get the link to a wall and the code to place it inside another page testimonial_walls_embed

The public address of the wall, and the script, frame and plain link snippets that place it inside another page. The snippets never change when the wall is edited.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/walls/:id/embed.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/walls.json List the Walls of Love of a site testimonial_walls_list

Every Wall of Love of the site that is not in the trash, with its address, what feeds it and how many testimonials it shows. Use q to search by name or slug and sort to order by name or by newest.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/walls.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/walls/:id/restore.json Restore a wall from the trash testimonial_walls_restore

Takes a wall out of the trash. Its page responds again at the same address, and every embedded copy shows it again without a change to the code that was pasted.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/walls/:id/restore.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/walls/:id.json Get one wall with every setting testimonial_walls_get

Everything the builder stores: the address (/w/<slug>), the header and the button, the layout, what feeds the wall, what each card shows, the style, and the search engine settings. A wall in the trash responds with 404.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/walls/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/walls/:id.json Change a wall testimonial_walls_update

Only the inputs sent change. A changed slug leaves the old address redirecting.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/walls/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Warehouse sync

POST /workspaces/:workspace_id/warehouses.json Add a warehouse destination warehouse_destinations_create

Adds a warehouse destination that receives each day's raw events (Business plan only). Daily at 02:00 UTC the previous UTC day is uploaded, the first run is at the next 02:00 UTC, and a failed run is retried an hour later. kind must be given and decides the required settings. s3 (also GCS, R2 and MinIO through endpoint) needs bucket, access_key_id and secret_access_key, with optional region, prefix and endpoint (https, public hosts only). bigquery needs service_account_json, dataset and table, with optional project_id. snowflake needs account, user, private_key (PEM RSA), database, schema and table, with optional warehouse and role. azure needs sas_url (an https blob container SAS link with write permission), with optional prefix. Secrets (secret_access_key, service_account_json, private_key, sas_url) are write only and are never returned. site_ids limits the sites (pa_... ids), empty means every site, and a member or token limited to some sites must name them. Event properties are only sent when marked safe to export (see event_properties_list). Needs the workspace.manage permission. Returns the destination with id (whd_...), a masked target and configured (the names of the filled in settings).

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/warehouses.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /warehouses/:id.json Delete a warehouse destination warehouse_destinations_delete

Deletes a warehouse destination so nothing more is sent. Data that was already uploaded stays in your bucket or warehouse, and this cannot be undone, although you can add the destination again (history is not resent automatically). Any manual sync still queued is not recalled. The id comes from warehouse_destinations_list. Needs the workspace.manage permission. Returns the deleted destination.

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/warehouses/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/warehouses.json List warehouse destinations warehouse_destinations_list

Lists the warehouse destinations of a workspace sorted by name, optionally only one kind (s3, bigquery, snowflake or azure). A member or token limited to some sites only sees destinations whose sites are all among theirs, and destinations that send every site are hidden from them. Paginated with page and per_page (up to 100). Each item has the fields described in warehouse_destinations_get. Needs the workspace_id from workspaces_list and the workspace.manage permission.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/warehouses.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /warehouses/:id.json Get a warehouse destination (no secrets) warehouse_destinations_get

Returns one warehouse destination without credentials. Fields are id (whd_...), kind (s3, bigquery, snowflake or azure), name, target (a masked summary like s3://bucket/prefix), configured (names of the settings that are filled in, secrets show only by name), site_ids (empty means every site), schedule (daily), schema_version (the row layout sent), last_run_at, next_run_at, last_error (null when the last run worked) and created_at. The id comes from warehouse_destinations_list. Check last_error after warehouse_destinations_sync to see how a sync went.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/warehouses/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /warehouses/:id/sync.json Queue a sync of one day now warehouse_destinations_sync

Queues an immediate upload of one UTC day of raw events to a destination, outside the daily schedule. Business plan only. date (YYYY-MM-DD) must be before today in UTC and defaults to yesterday. It uploads that day for every site of the destination, so avoid syncing a day that was already sent, and manual syncs are rate limited to 30 per hour per workspace. The call returns 202 with the destination at once and the upload runs in the background. Poll warehouse_destinations_get and read last_run_at and last_error to see the result. The id comes from warehouse_destinations_list. Needs the workspace.manage permission.

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/warehouses/:id/sync.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /warehouses/:id.json Update a warehouse destination (blank secrets are kept) warehouse_destinations_update

Changes a warehouse destination (Business plan only). Only the inputs you send change and the kind cannot be changed. A blank secret keeps the stored value, so you can edit other settings without sending credentials again. Settings are validated for the destination's kind as in warehouse_destinations_create. Sending site_ids replaces the list of sites. When the target changes (another bucket, dataset, table, database or similar) the progress of unfinished days starts over, so those days are sent again to the new place. The id comes from warehouse_destinations_list. Needs the workspace.manage permission. Returns the updated destination.

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/warehouses/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Webhooks

GET /workspaces/:workspace_id/webhooks/:webhook_id/deliveries.json List deliveries for a webhook webhook_deliveries_list

Lists the deliveries sent to one webhook (path takes webhook_id), newest first and paginated. Each has id (whd_...), event, status (pending, succeeded for any 2xx response, or failed), status_code (null when no response came), error (a short reason such as a timeout, otherwise null), attempt (how many tries so far), duration_ms, payload (the JSON data that was sent), response_body (first 2000 characters of the response) and delivered_at and created_at. Filter with status succeeded or failed. Use it to debug an endpoint and webhook_deliveries_redeliver to send one again.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/webhooks/:webhook_id/deliveries.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /workspaces/:workspace_id/webhooks/:webhook_id/deliveries/:id/redeliver.json Send a delivery again webhook_deliveries_redeliver

Sends the payload of a past delivery again as a new delivery to the same endpoint (id whd_..., from webhook_deliveries_list). This makes an HTTP request to the customer's URL, so the receiver may process the same event twice. It is rate limited per workspace (shared with webhooks_test). It is queued, so the response is a new delivery with status pending, read webhook_deliveries_list to see the outcome.

Permission: workspace.manage

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/webhooks/:webhook_id/deliveries/:id/redeliver.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces/:workspace_id/webhooks.json Add a webhook endpoint webhooks_create

Adds an endpoint that receives POSTed JSON for workspace events (path takes workspace_id). url must be a public https address, private and internal addresses are refused. events is a required list chosen from alert.triggered, alert.resolved, goal.completed_threshold, uptime.down, uptime.up, export.ready, import.completed, import.failed, site.created, site.deleted, member.added, usage.threshold and site_audit.completed. enabled defaults to true. A signing secret (whsec_...) is generated and returned in the response, and each delivery carries a Privatus-Signature header with a timestamp and an HMAC-SHA256 of the body made with it. Failed deliveries are retried with backoff and the endpoint is disabled after 20 failures in a row. Use webhooks_test to send a sample.

Permission: workspace.manage

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/webhooks.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /workspaces/:workspace_id/webhooks/:id.json Delete a webhook webhooks_delete

Deletes a webhook endpoint and its whole delivery log. This cannot be undone and pending retries stop. To pause it instead use webhooks_update with enabled false. The id (wh_...) comes from webhooks_list. Returns the deleted webhook.

Permission: workspace.manage

Example
curl -X DELETE https://privatusanalytics.com/workspaces/:workspace_id/webhooks/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/webhooks.json List webhooks webhooks_list

Lists the outgoing webhook endpoints of a workspace (path takes workspace_id), oldest first. Each has id (wh_...), url, events (the event names it receives), enabled, status (active, failing after recent failures, or disabled), secret (the signing secret, treat it as sensitive), consecutive_failures, last_success_at, last_failure_at and created_at. Needs the workspace.manage permission. This is for sending raw events to your own server. To notify people use notification_channels_list. Use webhook_deliveries_list for what was sent.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/webhooks.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /workspaces/:workspace_id/webhooks/:id.json Get a webhook webhooks_get

Returns one webhook endpoint by id (wh_..., from webhooks_list), including its signing secret (whsec_...), which is used to verify the Privatus-Signature header on each delivery. Fields are url, events, enabled, status (active, failing or disabled), secret, consecutive_failures, last_success_at, last_failure_at and created_at. Needs the workspace.manage permission.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/webhooks/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /workspaces/:workspace_id/webhooks/:id/test.json Send a test delivery webhooks_test

Queues a webhook.test delivery with a sample message to one endpoint, even when it is disabled, which makes an HTTP request to the customer's URL. It is rate limited per workspace (shared with webhook_deliveries_redeliver) and a test is not retried. The delivery is sent in the background, so the response returns the queued delivery with status pending. Read webhook_deliveries_list a moment later to see status_code, error, duration_ms and response_body.

Permission: workspace.manage

Example
curl -X POST https://privatusanalytics.com/workspaces/:workspace_id/webhooks/:id/test.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
PATCH /workspaces/:workspace_id/webhooks/:id.json Update a webhook (events, URL, enabled, rotate secret) webhooks_update

Changes a webhook (id from webhooks_list). Only the inputs you send change. Sending events replaces the whole list. Setting enabled true on a disabled endpoint resets its failure count, which is how you turn back on one that was disabled after repeated failures. Set rotate_secret true to generate a new signing secret, shown in the response, and the old one stops working at once so the receiver must be updated. The url rules are the same as webhooks_create. Returns the webhook.

Permission: workspace.manage

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/webhooks/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Website

GET /blog.json List blog posts blog_list

Lists the posts of the Privatus Analytics blog, newest first, with slug, title, kicker, rubric (the one-sentence summary), description, path and dates, and no body. To read one use blog_get with its slug. Paginated with page and per_page (maximum 100). Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/blog.json
GET /blog/:slug.json Read a blog post (Markdown and HTML) blog_get

Returns one blog post by its slug (from blog_list, the last part of /blog/<slug>) with its headings, its Markdown body and the rendered HTML. Posts are written in English. Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/blog/:slug.json
GET /changelog.json List changelog entries changelog_list

Published product updates, newest first. Also available as RSS at /changelog.rss.

Permission: public

Example
curl https://privatusanalytics.com/changelog.json
POST /contact.json Send a message to the Privatus Analytics team contact_create

Browser clients send a Cloudflare Turnstile token as `cf-turnstile-response`. Requests with an API token need none.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/contact.json \
  -H "Content-Type: application/json" -d '{…}'
POST /newsletter/confirm.json Confirm a newsletter subscription (signed token) newsletter_confirm

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/newsletter/confirm.json \
  -H "Content-Type: application/json" -d '{…}'
POST /newsletter.json Subscribe an email to the newsletter newsletter_subscribe

Browser clients send a Cloudflare Turnstile token as `cf-turnstile-response`. Requests with an API token need none.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/newsletter.json \
  -H "Content-Type: application/json" -d '{…}'
POST /newsletter/unsubscribe.json Unsubscribe with a signed link token newsletter_unsubscribe

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/newsletter/unsubscribe.json \
  -H "Content-Type: application/json" -d '{…}'
GET /pricing.json Get plans, prices and plan limits pricing_get

The same data as the pricing page: the events ladder, every plan limit and what counts as an event. Pass events to get the matching tier.

Permission: public

Example
curl https://privatusanalytics.com/pricing.json
GET /status.json Get the current system status status_get

Database, Redis and background-job health, checked at most every 15 seconds.

Permission: public

Example
curl https://privatusanalytics.com/status.json
GET /tools/header-checker.json Check a page’s Referrer-Policy and CSP for analytics tools_header_check

Fetches one public page and checks the response headers that affect analytics. Use it when tracking data is missing or referrers look wrong. It looks at Referrer-Policy, the Content-Security-Policy script-src and connect-src directives (whether Privatus Analytics may load and send data), Permissions-Policy and the number of Set-Cookie headers. Returns url (the final URL after up to 3 redirects), status (HTTP status) and findings, each with check, value (nullable), level (ok, warn or info) and a plain-language note. The URL must be http or https and public, private and internal addresses are refused, only the first 1 MB is read and the fetch times out after 5 seconds. Rate limited to 10 calls a minute per client. Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/tools/header-checker.json
GET /tools/privacy-policy-generator.json Draft a privacy-policy paragraph for Privatus Analytics tools_privacy_policy

Drafts a privacy policy paragraph describing how Privatus Analytics collects data on the customer's site (cookieless, no personal data stored, country only location). It is a starting point, not legal advice. Inputs are site_name (up to 120 characters), contact_email (optional, adds a contact sentence) and honors_gpc (default true, adds a sentence saying Do Not Track and Global Privacy Control are honored). Returns site_name, paragraphs (array of strings), text (the paragraphs joined with blank lines, with the address of Privatus Analytics in brackets after its name) and html (the paragraphs as <p> elements, with the name as a plain link). The text is written in the language of the request, English by default. It makes no changes to any account. Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/tools/privacy-policy-generator.json
GET /tools/qr-code.json Generate a QR code as SVG tools_qr_code

Generates a QR code as an SVG image for print or posters. Pair it with a link that has UTM parameters from tools_utm_builder. Inputs are text (the URL or text, up to 1,000 characters) and level (error correction l, m, q or h, default m, higher survives more damage but needs a denser code). Returns text, level, size (modules per side) and svg (a standalone SVG document, black on white). Text too long for the chosen level is rejected. For QR codes of tracked short links use links_qr instead. Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/tools/qr-code.json
GET /tools/tracker-scanner.json List cookies and known trackers on a page tools_tracker_scan

Fetches one public page and lists the cookies it sets and the known third-party trackers it references. Use it for a quick check of what a page loads before choosing a consent approach. It is a static scan of the HTML and response headers only, it does not run JavaScript, so cookies set by scripts and tags loaded later by a tag manager are missed. Returns url (final URL after up to 3 redirects), status, truncated (true when only the first 1 MB was read), cookies (name, domain, path, expires, secure, http_only and same_site, values are never returned), trackers (name, category, match and evidence, where category is advertising, analytics, session_recording, tag_manager, marketing or privacy_analytics) and third_party_hosts (sorted host names of other sites). The URL must be public http or https, with a 5 second fetch timeout, and calls are limited to 10 a minute per client. Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/tools/tracker-scanner.json
GET /tools/utm-builder.json Build a campaign URL with UTM parameters tools_utm_builder

Builds a campaign URL with UTM parameters and flags common mistakes. Inputs are url (must be http or https), source, medium and campaign (all required) and optional term and content. Existing utm_ parameters on the URL are replaced and other query parameters are kept. Returns url (the finished link), params (the utm_ values that were added, blank ones left out) and warnings (strings for values with uppercase letters, spaces, or text that looks like an email address or a long number, which should never be in a URL). Use lowercase values with no spaces so the same campaign is not split into several rows in reports. It saves nothing. Public, no authentication needed.

Permission: public

Example
curl https://privatusanalytics.com/tools/utm-builder.json
POST /waitlist.json Join a waitlist (EU hosting) waitlist_join

Browser clients send a Cloudflare Turnstile token as `cf-turnstile-response`. Requests with an API token need none.

Permission: public · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/waitlist.json \
  -H "Content-Type: application/json" -d '{…}'

Widgets

POST /sites/:site_id/testimonials/widgets/:id/copied.json Note that the pasted HTML embed of a widget was copied testimonial_widgets_mark_copied

The pasted HTML does not update by itself. After this call, testimonial_widgets_embed reports how many times the widget changed since.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/widgets/:id/copied.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/widgets.json Create a widget testimonial_widgets_create

One call carries the whole widget: the layout and its options, what feeds it, what each card shows and how it looks. Anything left out starts from a default, and a blank style setting inherits the brand kit. Follow it with testimonial_widgets_embed to get the code to paste.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/widgets.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /sites/:site_id/testimonials/widgets/:id.json Move a widget to the trash testimonial_widgets_trash

Its embeds render nothing and take no space until it is restored. It is deleted for good after 30 days. Over MCP the tool changes nothing and responds with the page of the app where a person does it.

Permission: content.write · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/sites/:site_id/testimonials/widgets/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /sites/:site_id/testimonials/widgets/:id/duplicate.json Copy a widget with every setting testimonial_widgets_duplicate

Makes a copy with a new id and the name ending in "copy". The copy has its own embed code and has not been seen live anywhere. The original is not changed.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/widgets/:id/duplicate.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/widgets/:id/embed.json Get the embed code of a widget and what it weighs testimonial_widgets_embed

Returns the script snippet (recommended), the pasted HTML, the frame code and the address of the public data, with the measured size of the loader script and of this widget. The pasted HTML and the frame are null for the corner popup.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/widgets/:id/embed.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/widgets.json List the widgets of a site testimonial_widgets_list

Widgets in the trash are not listed. Each widget carries its settings, how many testimonials it shows now, its views and clicks for the last 30 days and the first page it was seen live on.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/widgets.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /sites/:site_id/testimonials/widgets/:id/installs.json List the pages a widget was seen live on testimonial_widgets_installs

Up to 50 pages, the newest first. Only the script and the frame can be detected.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/widgets/:id/installs.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
POST /sites/:site_id/testimonials/widgets/:id/restore.json Restore a widget from the trash testimonial_widgets_restore

It comes back wherever it was pasted.

Permission: content.write

Example
curl -X POST https://privatusanalytics.com/sites/:site_id/testimonials/widgets/:id/restore.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /sites/:site_id/testimonials/widgets/:id.json Get one widget with every setting, what it shows and where it is live testimonial_widgets_get

Everything the builder stores: the layout and its options, what feeds the widget (a collection, a saved view, tags or a hand-picked list), what each card shows, the style, the language and the review markup, with where it was seen live and how many testimonials it shows now. A widget in the trash responds with 404.

Permission: analytics.read

Example
curl https://privatusanalytics.com/sites/:site_id/testimonials/widgets/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /sites/:site_id/testimonials/widgets/:id.json Change a widget testimonial_widgets_update

Only the inputs sent change. A saved change reaches every site the widget is on without touching their code, because its cached copies are cleared.

Permission: content.write

Example
curl -X PATCH https://privatusanalytics.com/sites/:site_id/testimonials/widgets/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'

Workspaces

GET /workspaces/:workspace_id/white_label.json Get the white label brand white_label_get

Returns the workspace's white label branding. Fields are workspace_id (ws_...), brand_name, brand_logo_url and brand_color (a hex color such as #06755b), each null when unset, hide_branding, available (true when the plan includes white label, which is Business and above) and active (true when branding is set and in effect). The branding replaces Privatus Analytics on shared dashboards, embeds, status pages and email reports. Requires workspace.manage.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/white_label.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:workspace_id/white_label.json Set the white label brand for shared dashboards and email reports (Business) white_label_update

Sets the workspace's white label branding. Business plan only, otherwise it fails with a plan error. Only the inputs you send change, and an empty string clears brand_name, brand_logo_url or brand_color. brand_logo_url must be an https URL to an image and brand_color a hex color such as #06755b. hide_branding true removes the Privatus Analytics link from shared dashboards, embeds and status pages and the logo from email reports. The change applies immediately to existing share links. Requires workspace.manage. Returns the branding (see white_label_get).

Permission: workspace.manage

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/white_label.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces/:workspace_id/security.json Get workspace security settings workspace_security_get

Returns the workspace security policy. Fields are workspace_id (ws_...), enforce_2fa (members must use two-factor authentication), login_methods (the allowed ones from password, magic_link, passkey, google, github, apple and sso), session_ttl_hours (sign-out after this many hours, null for no limit), app_ip_allowlist (IPs or CIDR ranges allowed to use the app and API, empty means anywhere) and support_access_until (ISO 8601 time until which Privatus Analytics support may view the workspace, null when no access is granted). Requires workspace.manage.

Permission: workspace.manage

Example
curl https://privatusanalytics.com/workspaces/:workspace_id/security.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:workspace_id/security.json Update workspace security settings (2FA, login methods, session lifetime, IP allowlist, support access) workspace_security_update

Changes the workspace security policy, only the inputs you send. enforce_2fa true requires every member to use two-factor authentication and needs the Business plan. login_methods replaces the allowed list with values from password, magic_link, passkey, google, github, apple and sso, and removing a method signs out members' remembered logins so they must log in again. session_ttl_hours signs members out after that many hours (0 removes the limit). app_ip_allowlist replaces the list of IPs or CIDR ranges allowed to reach the app and API (Business plan, an empty list means anywhere) and is refused if it would exclude the IP of the caller, so you cannot lock yourself out. support_access true lets Privatus Analytics support view the workspace for 7 days and false withdraws that access. Requires workspace.manage. Returns the policy (see workspace_security_get).

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/security.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
POST /workspaces.json Create a workspace (you become the owner) workspaces_create

Permission: member · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X POST https://privatusanalytics.com/workspaces.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
DELETE /workspaces/:id.json Delete a workspace and all its sites (owner only, cancels any subscription) workspaces_delete

Deletes a workspace together with all of its sites. It is a soft delete, everything disappears at once and is permanently purged after 7 days, so treat it as irreversible. Only the owner can do it, and confirm must be the workspace's exact name (case sensitive). A live paid subscription is canceled immediately first, and if that cancellation fails nothing is deleted. Pending site transfers from the workspace are canceled. Members lose access. Use workspaces_get to read the name. Requires workspace.manage and the owner role. Returns the workspace.

Permission: workspace.manage · HTTP only (the MCP tool, if any, only links to the app)

Example
curl -X DELETE https://privatusanalytics.com/workspaces/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'
GET /workspaces.json List your workspaces workspaces_list

Lists the workspaces the caller belongs to, ordered by name. An API token or MCP connection only sees the one workspace it was issued for. Each item has id (ws_..., used as workspace_id by most tools), name, timezone, currency, plan, plan_events, subscription_status and role (the caller's role). Call sites_list with a workspace id next to find site ids. Creating a workspace is only possible in the app.

Permission: member

Example
curl https://privatusanalytics.com/workspaces.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
GET /workspaces/:id.json Get a workspace workspaces_get

Returns one workspace by its public id (ws_..., from workspaces_list). Fields are id, name, timezone (IANA name), currency (ISO 4217), plan (free, pro, business or enterprise), plan_events (the monthly event allowance of the plan), subscription_status (none, trialing, active, past_due, paused or canceled), role (the caller's role in it) and created_at. Use billing_get for full billing details and billing_usage for this month's usage. Any member can read it.

Permission: member

Example
curl https://privatusanalytics.com/workspaces/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH /workspaces/:id.json Update workspace settings workspaces_update

Changes a workspace's name, default timezone, default currency or whether it takes part in benchmarks, only the inputs you send. timezone is an IANA name such as Europe/Berlin and currency is a 3-letter ISO 4217 code. These are defaults for new sites, they do not change existing sites (use sites_update for those). Requires workspace.manage. Returns the workspace (see workspaces_get).

Permission: workspace.manage

Example
curl -X PATCH https://privatusanalytics.com/workspaces/:id.json \
  -H "Authorization: Bearer $PRIVATUS_TOKEN" \
  -H "Content-Type: application/json" -d '{…}'