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.
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.
curl https://privatusanalytics.com/sites/pa_7Q2K9XH3AB/stats/aggregate.json?period=7d \
-H "Authorization: Bearer $PRIVATUS_TOKEN"
{
"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
Send Authorization: Bearer pat_…. Create tokens under Workspace settings, API tokens. Authentication guide.
Add .json to any path. Responses are { "data": … } and errors are { "error": { "code", "message" } }. Errors.
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)
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)
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
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
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
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
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)
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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)
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)
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)
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)
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)
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)
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
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)
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
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)
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
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
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
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
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
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
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
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
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
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
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
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
curl https://privatusanalytics.com/sites/:site_id/campaigns.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN"
POST
/sites/:site_id/campaigns/links.json
Save a UTM-tagged link to the library
links_create
Saves a UTM-tagged link to the site's link library (path takes site_id). name (up to 120 characters) and destination (an http or https URL up to 2048 characters) are required. The utm values are cleaned automatically, lowercased, trimmed and with spaces turned into underscores, and a value that looks like personal data (an email address for example) is rejected. Up to 20 tags are kept, comma separated entries are split and tags are lowercased and cut at 40 characters. It only stores the link, it does not track anything by itself. The returned url is the destination with the UTM parameters added, and a UTM parameter already in the destination is replaced. Use links_qr for a QR code.
Permission: content.write
curl -X POST https://privatusanalytics.com/sites/:site_id/campaigns/links.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
DELETE
/sites/:site_id/campaigns/links/:id.json
Delete a saved link
links_delete
Removes a saved link from the library. This only deletes the saved entry, URLs you already shared keep working and their visits are still counted by campaign. It cannot be undone. The id (lnk_...) comes from links_list. Returns the deleted link.
Permission: content.write
curl -X DELETE https://privatusanalytics.com/sites/:site_id/campaigns/links/:id.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
GET
/sites/:site_id/campaigns/links.json
List saved campaign links
links_list
Lists the saved campaign links (UTM-tagged URLs) of one site (path takes site_id), newest first and paginated. Each link has id (lnk_...), name, destination, utm_source, utm_medium, utm_campaign, utm_content, utm_term, url (the destination with the UTM parameters added, ready to share), tags, notes, created_at and updated_at. query searches name, destination and utm_campaign, tag keeps only links with that tag. The response meta has the pagination plus tags, every tag of the links of the site, sorted. This is the saved library, to see how campaigns performed use campaigns_list or stats_breakdown with dimension utm_campaign.
Permission: analytics.read
curl https://privatusanalytics.com/sites/:site_id/campaigns/links.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN"
GET
/sites/:site_id/campaigns/links/:id/qr.json
QR code (SVG) for a saved link
links_qr
Returns a QR code for a saved link's full tagged URL, for print. The response has link_id, url (the tagged URL encoded in the code) and svg (a complete standalone SVG document as a string, black on white). size is the module size in pixels, from 2 to 20 (default 6), which scales the image. The id (lnk_...) comes from links_list. Use links_get when you only need the URL.
Permission: analytics.read
curl https://privatusanalytics.com/sites/:site_id/campaigns/links/:id/qr.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN"
GET
/sites/:site_id/campaigns/links/:id.json
Get a saved campaign link
links_get
Returns one saved campaign link by id (lnk_..., from links_list). The response has name, destination, utm_source, utm_medium, utm_campaign, utm_content, utm_term (each null when unset), url (the full tagged URL), tags, notes, created_at and updated_at. Use links_qr for a print ready QR code of the same URL.
Permission: analytics.read
curl https://privatusanalytics.com/sites/:site_id/campaigns/links/:id.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN"
PATCH
/sites/:site_id/campaigns/links/:id.json
Change a saved link
links_update
Changes a saved campaign link (id from links_list). Only the inputs you send change. The same cleaning and personal data checks as links_create apply. Sending tags replaces the whole tag list. Send an empty string for a utm field to clear it. Returns the updated link with its recomputed url.
Permission: content.write
curl -X PATCH https://privatusanalytics.com/sites/:site_id/campaigns/links/:id.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
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
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
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
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
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
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
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
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
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
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
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
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
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
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
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)
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
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)
curl https://privatusanalytics.com/openapi.json
GET
/docs/search.json
Search the documentation
docs_search
Searches the documentation on the server and returns ranked pages. Use it to find how a feature works before guessing. q is split into up to 10 terms, every term must appear in the page, and title and heading matches rank highest. An empty q returns every page in the catalog order. limit defaults to 20 when q is given and is at most 100. Each result has title, path (use the section and page parts with docs_get), section, headings (list of id and text, useful as the heading anchor), excerpt and score (null when q is empty). Searches the language of the request, English by default. Public, no authentication needed.
Permission: public
curl https://privatusanalytics.com/docs/search.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
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
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
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
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
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
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
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
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
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
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
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
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)
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)
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)
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)
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)
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)
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)
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
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
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)
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
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
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
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
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
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)
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
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)
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
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
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
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)
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
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
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)
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
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
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
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
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
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)
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)
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
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
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
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
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.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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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)
curl https://privatusanalytics.com/wallboard/:token.json
POST
/sites/:site_id/live/wallboard/links.json
Create a wallboard link (read-only, revocable)
wallboard_links_create
Creates a read-only wallboard URL that anyone holding it can open without logging in, so share it only with people who should see these numbers. Path takes site_id and this site is always included. site_ids adds more sites to rotate through (pa_... ids you can access, up to 12 sites in total). rotate_seconds is how long each site stays on screen, from 5 to 300 (default 20, values outside are clamped). expires_at is an ISO 8601 date-time, or a plain YYYY-MM-DD date that means the end of that day in the site's timezone. The wallboard only shows live and today's aggregate numbers. Returns the link with its url. Revoke it with wallboard_links_delete.
Permission: sites.manage
curl -X POST https://privatusanalytics.com/sites/:site_id/live/wallboard/links.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
DELETE
/sites/:site_id/live/wallboard/links/:id.json
Revoke a wallboard link
wallboard_links_delete
Revokes a wallboard link so TVs that show it stop updating and the URL stops working. This cannot be undone, create a new link instead. Revoking an already revoked link does nothing. The id (shr_...) comes from wallboard_links_list. Returns the link with revoked_at set.
Permission: sites.manage
curl -X DELETE https://privatusanalytics.com/sites/:site_id/live/wallboard/links/:id.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
GET
/sites/:site_id/live/wallboard/links.json
List wallboard links for a site
wallboard_links_list
Lists the wallboard links of a site (path takes site_id), newest first and including revoked ones. A wallboard link is a secret URL that shows live visitors and today's numbers on a TV without a login. Each has id (shr_...), name, url, site_ids (pa_... ids of the sites it rotates through), rotate_seconds, expires_at, revoked_at (set once revoked) and created_at. Callers limited to some sites only see links whose sites are all theirs. For the wallboard data itself use live_wallboard. Needs the sites.manage permission.
Permission: sites.manage
curl https://privatusanalytics.com/sites/:site_id/live/wallboard/links.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN"
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)
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)
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
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)
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
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)
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)
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)
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)
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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)
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)
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
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
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)
curl -X PATCH https://privatusanalytics.com/workspaces/:workspace_id/sso.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
Search
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
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)
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
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
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
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
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
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
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
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)
curl https://privatusanalytics.com/badge/:site_id.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
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
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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
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)
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
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
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)
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
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
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
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)
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)
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)
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)
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
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)
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
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
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
curl -X PATCH https://privatusanalytics.com/sites/:id.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
Sponsor report
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)
curl https://privatusanalytics.com/sponsor/:token.json
Sponsor reports
POST
/sites/:site_id/experiments/ads/:ad_placement_id/reports.json
Create a sponsor report link for a placement
ad_report_links_create
A read-only link to the placement's numbers, for all of its creatives or the ones listed in creative_ids, for someone with no account. Over MCP the member is emailed that a link was made. Pro and up. Requires content.write. Returns the link with its url.
Permission: content.write
curl -X POST https://privatusanalytics.com/sites/:site_id/experiments/ads/:ad_placement_id/reports.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
DELETE
/sites/:site_id/experiments/ads/:ad_placement_id/reports/:id.json
Delete a sponsor report link
ad_report_links_delete
The address stops working at once. 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)
curl -X DELETE https://privatusanalytics.com/sites/:site_id/experiments/ads/:ad_placement_id/reports/:id.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'
GET
/sites/:site_id/experiments/ads/:ad_placement_id/reports.json
List a placement's sponsor report links
ad_report_links_list
The links, with their address and when they expire. Requires analytics.read.
Permission: analytics.read
curl https://privatusanalytics.com/sites/:site_id/experiments/ads/:ad_placement_id/reports.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN"
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)
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)
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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
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)
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
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
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)
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
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
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
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
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
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
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
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
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)
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
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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)
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
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
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)
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
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
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
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
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)
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)
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)
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
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)
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
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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
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
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
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
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)
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
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
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
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
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)
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
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
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
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)
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)
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
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
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
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
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
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
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
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
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
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)
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
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
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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)
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
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
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
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)
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
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
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
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
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
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
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)
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)
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
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
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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)
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)
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)
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)
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
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
curl https://privatusanalytics.com/status.json
GET
/tools/consent-gap.json
Estimate traffic missed by consent-based analytics
tools_consent_gap
Free calculator that estimates how much traffic a cookie-consent analytics tool misses compared with cookieless tracking. It is a simple model, not a measurement. Inputs are monthly_visitors (a whole number above 0), consent_rate (percent of visitors who accept the banner, 0 to 100) and adblock_share (percent whose blockers stop the script, 0 to 100, default 25). Returns the inputs plus measured_with_consent (visitors the consent tool sees), missed (visitors it does not see), missed_percent (one decimal), measured_cookieless (visitors reachable without a banner) and recovered_by_cookieless (visitors gained over consent tracking). All visitor counts are whole numbers. It makes no requests and uses no account data. Public, no authentication needed.
Permission: public
curl https://privatusanalytics.com/tools/consent-gap.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
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
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
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
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
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)
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
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
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)
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
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
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
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
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
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
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
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
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
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
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)
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)
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)
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
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
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
curl -X PATCH https://privatusanalytics.com/workspaces/:id.json \
-H "Authorization: Bearer $PRIVATUS_TOKEN" \
-H "Content-Type: application/json" -d '{…}'