{"openapi": "3.1.0", "info": {"title": "Metric Sensei Admin API", "version": "2", "summary": "Manage your Metric Sensei agency and clients, as a REST API and as MCP tools.", "description": "The Admin API lets you manage your agency and clients from your own code or an AI agent:\nclients, their people and access, data sources (Google, Shopify and Meta accounts) and the connections\nthat use them for Google Analytics, Tag Manager, Search Console, Business Profile, Merchant Center, Ads, AdSense, PageSpeed Insights, Shopify, Meta Ads, Facebook Pages, Instagram, Meta Business, WordPress, CallRail, Microsoft Advertising, Bing Webmaster Tools, Supermetrics, Salesforce, MNTN, RingCentral (RingEX phone system, RingCX contact center), HubSpot, TechShip, Mailchimp, Gorgias and Klaviyo, API/MCP Gateway keys, connected apps, sign-in settings, website tracking and billing.\n\nEvery call acts as **you**, with the role you have in the admin page. Agency admins manage their\nagency's people, billing and every client it owns, and can create clients in it. Client admins\nmanage the clients they manage, including their people. Everyone can see their own access and\nconnected apps, and list their API/MCP Gateway keys and API tokens. Platform administration (all\naccounts, environment settings, pricing and billing runs) is done in the admin page only.\n\n**API keys are read-only here.** For security, API/MCP Gateway keys and API tokens are created and\nrevoked only in the admin page, after you confirm it's you with a passkey or your authenticator\ncode. This API can list them (never their secret values) but can't create, change or revoke them.\n\n**Lists** take `limit` (default 100, max 500) and `cursor`, and return `next_cursor` (null on the\nlast page). **Deletes** answer `204 No Content`. **Errors** answer\n`{\"error\": \"<code>\", \"message\": \"...\", \"request_id\": \"...\"}`.\n\nSign in with a personal API token from the admin page (**API & agents**), sent as\n`Authorization: Bearer msat_...`. Read-only tokens can call `GET` operations only.\n\nThe same operations are available to AI apps and agents as MCP tools at `/mcp`. There, AI apps (Claude,\nChatGPT and others) can also **sign in** with your Metric Sensei account instead of sending a token\n(OAuth 2.1 with PKCE; discovery at `/.well-known/oauth-protected-resource/mcp`). You choose read only or\nread and change when signing in, and the app acts as you. See the guide at `/docs`.\n\n**Versions.** Every deploy is a new API version, numbered 1, 2, 3 and so on. Without a version\nyou get the latest. To pin one, put `/v<N>` before the path (`/v3/clients`, `/v3/mcp`) or send\n`Metric-Sensei-Version: <N>`. `GET /versions` lists the live versions. Old versions keep\nworking until they're retired; a retired version answers `410 Gone`.\n", "contact": {"name": "Metric Sensei", "url": "https://metricsensei.com"}}, "servers": [{"url": "https://api.metricsensei.com", "description": "This environment, latest version"}, {"url": "https://api.metricsensei.com/v2", "description": "This environment, version 2"}, {"url": "https://api.stage.metricsensei.com", "description": "Stage"}, {"url": "https://api.dev.metricsensei.com", "description": "Dev"}], "security": [{"token": []}], "tags": [{"name": "Me", "description": "Your own access, API/MCP Gateway keys and API tokens (list only), and connected apps. Everyone can use these."}, {"name": "Agencies", "description": "An agency owns, manages and pays for a set of clients. A customer with one client has a solo agency of its own."}, {"name": "Clients", "description": "Clients are the companies or sites whose data the API/MCP Gateway serves."}, {"name": "Data sources", "description": "A client's accounts (Google, Facebook, Microsoft, Salesforce and RingCentral sign-ins, Shopify stores, HubSpot, Mailchimp, Gorgias and Klaviyo accounts, CallRail, Bing Webmaster, Supermetrics, MNTN and TechShip keys) with their credentials. A client can have many of each; services use them through connections."}, {"name": "Connections", "description": "How each service (Google Analytics 4, Tag Manager, Search Console, Business Profile, Merchant Center, Ads, AdSense, PageSpeed Insights, Shopify, Meta Ads, Facebook Pages, Instagram, Meta Business, WordPress, CallRail, Microsoft Advertising, Bing Webmaster Tools, Supermetrics, Salesforce, MNTN, RingEX, RingCX, HubSpot, TechShip, Mailchimp, Gorgias, Klaviyo) uses one of the client's data sources, with its own settings."}, {"name": "Gateway keys", "description": "Keys that let a person use a client's connections through the API/MCP Gateway. Not the same as API tokens. List only; keys are created and revoked in the admin page."}, {"name": "Client users", "description": "The people in a client and which connections each one can use."}, {"name": "Connected apps", "description": "Apps (for example AI assistants) signed in to the API/MCP Gateway for a client."}, {"name": "Identity", "description": "A client's company sign-in (SSO), email domains and social sign-in choices."}, {"name": "Tracking", "description": "Tracked sites (websites, iOS and Android apps, APIs) shared by services, websites' tags and snippets, attribution settings and the dashboard. The /attribution/sites endpoints are the websites with Attribution on."}, {"name": "Privacy requests", "description": "A client's website visitor asks to see or delete their data. Log the request, check who is asking, search every service, then export or delete; the client answers the visitor."}, {"name": "Billing", "description": "Usage, invoices (one per agency per month, broken down by client), payments, balance and saved cards."}, {"name": "Pricing", "description": "Monthly base fee per service per client, free usage included, and prices above it."}, {"name": "Activity", "description": "Who changed what, and when, in a client or agency (admin page, this API and API/MCP Gateway write tools)."}, {"name": "Usage", "description": "API/MCP Gateway tool calls per day, per service and per client (successful calls are billed, failed ones are not), and the numbers on a client's and an agency's Home."}, {"name": "SEO", "description": "SEO (rank tracking, keyword research, site audits, backlinks, competitors, local grids, content briefs, AI visibility, alerts, reports, link prospects, log analysis) for clients with SEO on. One route runs every SEO operation; the operations and their arguments are the same as the API/MCP Gateway's seo_* tools."}, {"name": "Analytics", "description": "Website analytics for a client with Analytics on: traffic, channels, pages, goals, revenue, funnels, cohorts, heatmaps, recordings, A/B tests and the comparison with GA4. Everything the Analytics screen shows, run as you; the Analytics service checks your role in the client on every call. Most take a date range: `range` (today, yesterday, last_7_days, last_28_days, last_30_days, last_90_days, this_month, last_month, this_year, last_12_months or custom with `start` and `end`), in the website's own time zone. Settings (goals, funnels, linked sources) are under Tracking."}, {"name": "Offline Conversions", "description": "Offline Conversions for a client with it on: rules that decide which orders, leads, calls and won deals are sent to Google Ads, Meta, Microsoft Ads and Google Analytics 4, previews, test sends, the send log, the do-not-send list and the conversions assistant. Everything the Offline Conversions screen does, run as you; the service checks your role in the client on every call (changes need a client or agency admin)."}, {"name": "Website Optimizer", "description": "Website Optimizer for a client with it on: the websites it checks each day, their recommendations with one-click fixes and A/B tests, scores, reports and alerts. Everything the Website Optimizer screen does, run as you; the service checks your role in the client on every call (changes need a client or agency admin)."}, {"name": "Dashboards", "description": "Live dashboards for a client with Dashboards on: list, build and edit them, read their tiles' data, share them with people or by public link, send them by daily email and export them. Everything the Dashboards screen does, run as you; the Dashboards service checks your role in the client on every call."}, {"name": "Network Monitoring", "description": "Network Monitoring (NMS) for a client with it on - locations and their collectors, devices, cloud monitors of websites and data sources, alarms, outages, notification rules and maintenance windows. Every operation the Network page offers; the NMS service checks your role in the client on every call (setup changes need a client or agency admin)."}, {"name": "About", "description": "What this environment offers."}], "paths": {"/meta": {"get": {"operationId": "get_meta", "x-handler": "meta", "x-roles": ["everyone"], "tags": ["About"], "summary": "Describe this environment", "description": "Your role, the services you can connect (with their settings and tools), and whether Google, Shopify, Facebook, Microsoft, Salesforce, HubSpot, Gorgias and Klaviyo connect are set up.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/alerts": {"get": {"operationId": "list_alerts", "x-handler": "list_alerts", "x-list": {"key": "alerts"}, "x-roles": ["everyone"], "tags": ["Data sources"], "summary": "List data sources that need attention", "description": "Data sources whose Google or Facebook sign-in or Shopify access stopped working (severity `error`), or that\nno one has signed in to yet (severity `warning`), in every client you manage. Errors come first.\nPeople who don't manage a client get an empty list. Fix one with `start_data_source_sign_in`.\nSetup problems (`kind: setup`) are settings on the platform's side that stop a feature (severity `error`)\nor make it work less well (`warning`); `features` names them and `get_data_source_setup_checks` has the\nsteps to fix them.\n", "responses": {"200": {"description": "Alerts", "content": {"application/json": {"schema": {"type": "object", "properties": {"alerts": {"type": "array", "items": {"$ref": "#/components/schemas/Alert"}}, "count": {"type": "integer"}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/me": {"get": {"operationId": "get_my_access", "x-handler": "me", "x-roles": ["everyone"], "tags": ["Me"], "summary": "Show what you can use", "description": "Your clients, your role in each, and the connections and tools you can use there.", "responses": {"200": {"description": "Your access", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MyAccess"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/me/gateway-keys": {"get": {"operationId": "list_my_gateway_keys", "x-handler": "my_keys", "x-path": "/me/keys", "x-list": {"key": "keys", "as": "gateway_keys"}, "x-roles": ["everyone"], "tags": ["Me", "Gateway keys"], "summary": "List your API/MCP Gateway keys", "responses": {"200": {"description": "Your API/MCP Gateway keys", "content": {"application/json": {"schema": {"type": "object", "properties": {"gateway_keys": {"type": "array", "items": {"$ref": "#/components/schemas/GatewayKey"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/me/connected-apps": {"get": {"operationId": "list_my_connected_apps", "x-handler": "my_sessions", "x-path": "/me/sessions", "x-list": {"key": "sessions", "as": "connected_apps"}, "x-roles": ["everyone"], "tags": ["Me", "Connected apps"], "summary": "List apps signed in as you", "description": "Apps (such as AI assistants) you have signed in as you: to the API/MCP Gateway's /mcp in one client (`api` is `gateway`), or to this API's /mcp (`api` is `admin`, with `access` `read` or `write`).\n", "responses": {"200": {"description": "Your connected apps", "content": {"application/json": {"schema": {"type": "object", "properties": {"connected_apps": {"type": "array", "items": {"$ref": "#/components/schemas/ConnectedApp"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/me/connected-apps/{client}/{session_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/session_id"}], "delete": {"operationId": "sign_out_my_connected_app", "x-handler": "revoke_my_session", "x-path": "/me/sessions/{client}/{session_id}", "x-roles": ["everyone"], "tags": ["Me", "Connected apps"], "summary": "Sign out one of your connected apps", "description": "For apps signed in to the API/MCP Gateway (`api` is `gateway`). Use sign_out_my_admin_api_app for this API's.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/me/admin-api-apps/{session_id}": {"parameters": [{"$ref": "#/components/parameters/session_id"}], "delete": {"operationId": "sign_out_my_admin_api_app", "x-handler": "revoke_my_admin_api_session", "x-path": "/me/admin-api-sessions/{session_id}", "x-roles": ["everyone"], "tags": ["Me", "Connected apps"], "summary": "Sign out an app signed in to this API", "description": "For connected apps whose `api` is `admin` (signed in to this API's /mcp). Its tokens stop working at once.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/me/notifications": {"get": {"operationId": "get_my_notifications", "x-handler": "my_notifications", "x-roles": ["everyone"], "tags": ["Me"], "summary": "Get your email notification settings", "description": "`setup_digest`: whether you get the daily email listing data source setup problems that stop features in the clients and agencies you manage (on unless you turned it off).\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "set_my_notifications", "x-handler": "put_my_notifications", "x-roles": ["everyone"], "tags": ["Me"], "summary": "Change your email notification settings", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["setup_digest"], "properties": {"setup_digest": {"type": "boolean", "description": "Daily email about data source setup problems"}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/setup-checks": {"get": {"operationId": "setup_checks_overview", "x-handler": "setup_checks_overview", "x-roles": ["everyone"], "tags": ["Data sources"], "summary": "Setup problems per client", "description": "For every client you manage: how many data source setup checks need fixing (`blocking`: a feature stops working) or are worth fixing (`warning`). Counts only features of services the client has on. Worst first.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/me/profile": {"get": {"operationId": "get_my_profile", "x-handler": "my_profile", "x-roles": ["everyone"], "tags": ["Me"], "summary": "Get your profile", "description": "Your details (first and last name, email, phone and postal address), how you sign in, and which required details are still missing. Changing your password and adding or removing passkeys need your own sign-in, so they are done on the admin page's Profile tab.\n", "responses": {"200": {"description": "Your profile", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Profile"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "patch": {"operationId": "update_my_profile", "x-handler": "update_my_profile", "x-roles": ["everyone"], "tags": ["Me"], "summary": "Change your details", "description": "Send only the details that change. First and last name, phone, street address (line1), city, postal code and country are required, so afterwards they must all be filled in (send the missing ones together). Your email is your sign-in and can't be changed here. time_zone (where the admin page shows dates and times) can be changed on its own.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProfileDetails"}}}}, "responses": {"200": {"description": "Your updated profile", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Profile"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/me/tokens": {"get": {"operationId": "list_my_api_tokens", "x-handler": "my_tokens", "x-list": {"key": "tokens"}, "x-roles": ["everyone"], "tags": ["Me"], "summary": "List your API tokens", "description": "Your personal tokens for this API and its MCP endpoint. Tokens are created and revoked only in the admin page.", "responses": {"200": {"description": "Your API tokens", "content": {"application/json": {"schema": {"type": "object", "properties": {"tokens": {"type": "array", "items": {"$ref": "#/components/schemas/ApiToken"}}, "api": {"type": "object", "additionalProperties": true}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/clients": {"get": {"operationId": "list_clients", "x-handler": "list_tenants", "x-list": {"key": "tenants", "as": "clients"}, "x-roles": ["admin", "tenant_admin", "service_user"], "tags": ["Clients"], "summary": "List clients", "description": "Global admins see every client. Client admins see the clients they manage. Client users see the clients where a client admin gave them admin service access, with `my_services` saying what they may do there.", "responses": {"200": {"description": "Clients", "content": {"application/json": {"schema": {"type": "object", "properties": {"clients": {"type": "array", "items": {"$ref": "#/components/schemas/Client"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}, "post": {"operationId": "create_client", "x-handler": "create_tenant", "x-roles": ["admin", "agency_admin"], "tags": ["Clients"], "summary": "Create a client", "description": "Global admins can create a client in any agency, or leave out `agency_id` to give it a solo\nagency of its own. Agency admins create clients in an agency they manage (`agency_id` may be\nleft out if they manage only one).\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["client"], "properties": {"client": {"$ref": "#/components/schemas/ClientId"}, "name": {"type": "string", "description": "Display name. Defaults to the client ID."}, "agency_id": {"$ref": "#/components/schemas/AgencyId"}, "contact": {"$ref": "#/components/schemas/Contact"}, "invite_email": {"type": "string", "format": "email", "description": "Email a one-time link (7 days) inviting this person to add the client's details, set up a sign-in and become its client admin."}, "trial": {"type": "boolean", "description": "With invite_email and no agency_id: start a free trial when the invite is accepted."}}}}}}, "responses": {"201": {"description": "The new client", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Client"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/activity": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_client_activity", "x-handler": "tenant_activity", "x-roles": ["admin", "tenant_admin"], "tags": ["Activity"], "summary": "See who changed what in a client", "description": "Every change made in the admin page (its own screens and the paid services' screens), through this API or by an API/MCP Gateway write tool, and every super admin log-in to the client, newest first. Normal reads aren't logged. Kept for a year. Secrets are never logged. Filter by person, source, kind, action, role and dates, and search the words with `q`.", "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}, {"$ref": "#/components/parameters/activity_actor"}, {"$ref": "#/components/parameters/activity_via"}, {"$ref": "#/components/parameters/activity_kind"}, {"$ref": "#/components/parameters/activity_action"}, {"$ref": "#/components/parameters/activity_role"}, {"$ref": "#/components/parameters/activity_q"}, {"$ref": "#/components/parameters/activity_since"}, {"$ref": "#/components/parameters/activity_until"}], "responses": {"200": {"description": "Changes, newest first", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ActivityPage"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/activity": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "list_agency_activity", "x-handler": "agency_activity", "x-roles": ["admin", "agency_admin"], "tags": ["Activity"], "summary": "See who changed what in an agency and its clients", "description": "Changes to the agency itself and to every client it owned at the time, newest first, with the same filters and search as a client's log. Kept for a year.", "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}, {"$ref": "#/components/parameters/activity_actor"}, {"$ref": "#/components/parameters/activity_via"}, {"$ref": "#/components/parameters/activity_kind"}, {"$ref": "#/components/parameters/activity_action"}, {"$ref": "#/components/parameters/activity_role"}, {"$ref": "#/components/parameters/activity_q"}, {"$ref": "#/components/parameters/activity_since"}, {"$ref": "#/components/parameters/activity_until"}], "responses": {"200": {"description": "Changes, newest first", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ActivityPage"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/me/activity": {"get": {"operationId": "list_my_activity", "x-handler": "my_activity", "x-roles": ["everyone"], "tags": ["Activity"], "summary": "See your own sign-ins and changes", "description": "Every time you signed in (the admin page, an AI app or the Admin API's sign-in) and every change you made, anywhere on the platform, newest first, with the same filters and search as a client's log. Kept for a year.", "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}, {"$ref": "#/components/parameters/activity_actor"}, {"$ref": "#/components/parameters/activity_via"}, {"$ref": "#/components/parameters/activity_kind"}, {"$ref": "#/components/parameters/activity_action"}, {"$ref": "#/components/parameters/activity_role"}, {"$ref": "#/components/parameters/activity_q"}, {"$ref": "#/components/parameters/activity_since"}, {"$ref": "#/components/parameters/activity_until"}], "responses": {"200": {"description": "Your sign-ins and changes, newest first", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ActivityPage"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/audit": {"get": {"operationId": "list_audit_log", "x-handler": "platform_audit", "x-roles": ["everyone"], "tags": ["Activity"], "summary": "See the audit log you're allowed to see", "description": "The audit log, newest first, scoped to you. Super admins see everyone's: every person's sign-ins, super admins' log-ins to clients and agencies, every change in any client or agency, platform settings, and each stack deploy in both Regions (and can narrow it with `client` or `agency`). Agency admins see their agency's trail, which includes all its clients (`client` narrows it to one; with more than one agency, `agency` picks which). Client admins see their client's trail (with more than one client, `client` picks which). Anyone else sees only their own sign-ins and changes. Asking for a client or agency you can't see is refused. Without dates a super admin's view covers the last year; one request looks at a limited number of entries, so a narrow search may return a short (even empty) page with a `next_cursor`: keep going until it is null.", "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}, {"$ref": "#/components/parameters/activity_actor"}, {"$ref": "#/components/parameters/activity_via"}, {"$ref": "#/components/parameters/activity_kind"}, {"$ref": "#/components/parameters/activity_action"}, {"$ref": "#/components/parameters/activity_role"}, {"$ref": "#/components/parameters/activity_q"}, {"$ref": "#/components/parameters/activity_since"}, {"$ref": "#/components/parameters/activity_until"}, {"name": "client", "in": "query", "description": "Only entries in this client (one you can see).", "schema": {"type": "string"}}, {"name": "agency", "in": "query", "description": "Only entries in this agency and its clients (one you can see).", "schema": {"type": "string"}}], "responses": {"200": {"description": "Entries, newest first", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ActivityPage"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/usage": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_client_usage", "x-handler": "tenant_usage", "x-roles": ["admin", "tenant_admin"], "tags": ["Usage"], "summary": "See a client's API/MCP Gateway tool calls per day and per service", "description": "Successful API/MCP Gateway tool calls (the ones that are billed) for each of the last `days` days (UTC, today included), split by service, plus failed calls, which are never billed. `month` is this month's billed total next to the free calls the API/MCP Gateway's base fee includes (`included`, null if the API/MCP Gateway has no price). Calls made before November 2026 have no service and are listed as `other`.", "parameters": [{"$ref": "#/components/parameters/usage_days"}], "responses": {"200": {"description": "Daily calls, oldest first", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ClientUsage"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/usage": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "get_agency_gateway_usage", "x-handler": "agency_usage", "x-roles": ["admin", "agency_admin"], "tags": ["Usage"], "summary": "See API/MCP Gateway tool calls for each of an agency's clients", "description": "Each client's successful and failed API/MCP Gateway tool calls over the last `days` days, busiest first, with its calls per day, plus the agency's total per day. For charges, see the billing usage estimate.", "parameters": [{"$ref": "#/components/parameters/usage_days"}], "responses": {"200": {"description": "Calls per client", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgencyUsage"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/summary": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_client_summary", "x-handler": "tenant_summary", "x-roles": ["admin", "tenant_admin"], "tags": ["Usage"], "summary": "See the numbers on a client's Home", "description": "One entry per Home tile, only for the paid services the client has on: API/MCP Gateway calls per day for the last 30 days (UTC), Attribution sessions, submits and people identified, data sources and how many need a new sign-in, live events received, Dashboards and AI Services usage this month. Agency admins (and super admins) also get the client's share of this month's bill so far (`client_bill`, an estimate until the month closes). Tiles with nothing to show are left out; a tile that couldn't be read has `error` and no `data`.", "parameters": [{"$ref": "#/components/parameters/summary_tiles"}, {"$ref": "#/components/parameters/summary_lazy"}], "responses": {"200": {"description": "The client's Home tiles, in page order", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HomeSummary"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/seo/{op}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "op", "in": "path", "required": true, "description": "The SEO operation. Reads (GET): projects, project, overview, keywords, keyword_history, serp_snapshot, rank_summary, rank_trend, audits, audit, audit_issue, audit_pages, audit_page, crawl_summary, competitors, grid_runs, grid, local_profile, briefs, brief, search_console, paid_overlap, organic_pages, ai_visibility, alerts, prospects, prospect_verify, log_analyses, log_analysis, disavow_file. Research, billed as lookups: keyword_overview, keyword_ideas, serp, domain_overview, ranked_keywords, competitor_discover, keyword_gap, backlinks_summary, backlinks, referring_domains, anchors, backlinks_new_lost, backlink_gap. Changes and paid runs (POST; client admins for changes): project_create, project_update, project_delete, keywords_add, keywords_remove, keywords_tag, audit_start, grid_run, content_brief, content_grade, ai_check, report_pdf, prospects_add, prospects_from_gap, prospect_update, prospect_remove, log_upload, webhook_secret.", "schema": {"type": "string", "pattern": "^[a-z0-9_]{2,40}$"}}], "get": {"operationId": "get_seo", "x-handler": "seo_call", "x-roles": ["everyone"], "tags": ["SEO"], "summary": "Read SEO data for a client", "description": "Runs one read or research SEO operation with the query string as its arguments (lists as comma-separated text), e.g. `keywords?project_id=p_abc` or `keyword_ideas?seed=plumber%20austin&kind=questions`. Anyone with a role in the client may read; the client must have SEO on. Research operations are billed as SEO lookups.", "parameters": [{"name": "project_id", "in": "query", "required": false, "schema": {"type": "string"}, "description": "The SEO project (one website); `projects` lists them"}], "responses": {"200": {"description": "The operation's answer", "content": {"application/json": {"schema": {"type": "object", "properties": {"result": {"description": "The operation's data (shape depends on the operation)"}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "run_seo_operation", "x-handler": "seo_call", "x-roles": ["everyone"], "tags": ["SEO"], "summary": "Change SEO settings or run an SEO job for a client", "description": "Runs one SEO operation with the JSON body as its arguments, e.g. `keywords_add` with `{\"project_id\": \"p_abc\", \"keywords\": [\"plumber austin\"], \"tags\": [\"money\"]}` or `audit_start` with `{\"project_id\": \"p_abc\"}`. Changes to projects, keywords, prospects and webhooks need a client admin or agency admin; paid runs (audits, local grids, content tools, AI checks) are billed to the client.", "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "responses": {"200": {"description": "The operation's answer", "content": {"application/json": {"schema": {"type": "object", "properties": {"result": {"description": "The operation's data (shape depends on the operation)"}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/summary": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "get_agency_summary", "x-handler": "agency_summary", "x-roles": ["admin", "agency_admin"], "tags": ["Usage"], "summary": "See the numbers on an agency's Home", "description": "The agency's free trial (while one runs), its bill (this month so far, plan, balance owed and when services would pause) and each service's numbers added up over its active clients. A service's tile shows only when at least one active client has that service on.", "parameters": [{"$ref": "#/components/parameters/summary_tiles"}, {"$ref": "#/components/parameters/summary_lazy"}], "responses": {"200": {"description": "The agency's Home tiles, in page order", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HomeSummary"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/gateway-keys": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_gateway_keys", "x-handler": "list_keys", "x-path": "/clients/{client}/keys", "x-list": {"key": "keys", "as": "gateway_keys"}, "x-roles": ["admin", "tenant_admin"], "tags": ["Gateway keys"], "summary": "List a client's API/MCP Gateway keys", "responses": {"200": {"description": "API/MCP Gateway keys", "content": {"application/json": {"schema": {"type": "object", "properties": {"gateway_keys": {"type": "array", "items": {"$ref": "#/components/schemas/GatewayKey"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/clients/{client}/data-sources": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_data_sources", "x-handler": "list_data_sources", "x-list": {"key": "data_sources"}, "x-roles": ["admin", "tenant_admin", "service_user"], "tags": ["Data sources"], "summary": "List a client's data sources", "responses": {"200": {"description": "Data sources (never with credentials), each with the connections that use it", "content": {"application/json": {"schema": {"type": "object", "properties": {"data_sources": {"type": "array", "items": {"$ref": "#/components/schemas/DataSource"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/clients/{client}/data-sources/check": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "check_data_sources", "x-handler": "check_data_sources", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Check that a client's data sources can still sign in", "description": "Tests every data source's Google or Facebook sign-in or Shopify store access now (we also do this every\nhour) and saves the result in each source's `health`. Nothing is changed in Google, Shopify or Meta.\nReturns the data sources and any alerts. To fix an alert, use `start_data_source_sign_in`.\n", "responses": {"200": {"description": "The checked data sources and the alerts that remain", "content": {"application/json": {"schema": {"type": "object", "properties": {"data_sources": {"type": "array", "items": {"$ref": "#/components/schemas/DataSource"}}, "alerts": {"type": "array", "items": {"$ref": "#/components/schemas/Alert"}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "put": {"operationId": "save_data_source", "x-handler": "put_data_source", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Add or change a data source", "description": "Creates the data source if it doesn't exist (send `type` and `credentials`), otherwise changes\nonly the fields you send. New credentials replace the old ones for every connection using the\nsource. Credentials are stored encrypted and never returned. To use a Google, Facebook, Microsoft or\nSalesforce sign-in or a Shopify, HubSpot, Gorgias or Klaviyo install instead of pasting credentials, use\n`start_data_source_sign_in`.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"type": {"type": "string", "enum": ["google", "shopify", "meta", "wordpress", "callrail", "microsoft_ads", "bing_webmaster", "supermetrics", "blazeo", "salesforce", "mntn", "ringcentral", "netsuite", "hubspot", "snapfulfil", "techship", "mailchimp", "gorgias", "klaviyo"], "description": "The kind of account. Can't change later. A new `blazeo` source takes no credentials: the reply holds its webhook URL (`webhook_url`, shown only once), the method, format and the field names to send to Blazeo support."}, "api_key": {"type": "string", "description": "CallRail, Bing Webmaster Tools, Supermetrics and MNTN only, instead of `credentials`: a\nCallRail API v3 key (CallRail > Settings > Integrations > API Keys), a Bing Webmaster Tools\nAPI key (Bing Webmaster > Settings > API access > API Key), a Supermetrics API key\n(Supermetrics Hub > API keys) or an MNTN Reporting API key (MNTN > My Account > API). It is\ntried before saving; a key that is refused is rejected. TechShip: the portal's API key\n(`x-api-key`), sent with `secret_key`. Mailchimp: an API key (Mailchimp > Profile > Extras >\nAPI keys; it ends in the account's data center, for example `-us21`), tried before saving;\nthe usual way is Connect Mailchimp (`start_data_source_sign_in`). Gorgias: a user's REST API\nkey (Gorgias > Settings > REST API), sent with `email` and `domain`; live events need an\nadmin's key.\n"}, "email": {"type": "string", "description": "Gorgias only, with `api_key`: the email of the Gorgias user the key belongs to."}, "domain": {"type": "string", "description": "Gorgias only: the account name, the `acme` in acme.gorgias.com (the full address works too)."}, "account_id": {"type": "string", "description": "NetSuite only: the account ID (NetSuite > Setup > Company > Company Information), for example\n`1234567` or `1234567_SB1` for a sandbox. With `client_id` on a new source we make the\ncertificate: the reply's `netsuite.certificate_pem` is what the client uploads in NetSuite\n(OAuth 2.0 Client Credentials Setup). See `GET .../netsuite`.\n"}, "client_id": {"type": "string", "description": "NetSuite only, with `account_id`: the Client ID of the client's integration record (Client Credentials grant, REST web services scope)."}, "certificate_id": {"type": "string", "description": "NetSuite only: the Certificate ID NetSuite shows after the certificate is uploaded and mapped.\nWe sign in once and run one query before saving it; a refusal is rejected (400). After\n`new_certificate` it swaps the new certificate in.\n"}, "new_certificate": {"type": "boolean", "description": "NetSuite only. Same as `POST .../netsuite/certificate`."}, "api_url": {"type": "string", "description": "Snapfulfil only: the API address Snapfulfil gave you, `https://<name>.snapfulfil.net` (live\nor test). Send with `username` and `password` for a new source; on an existing one, send\nonly the fields that change. The sign-in is tried before saving. TechShip: the portal's API\naddress, a region (`us`, `ca` or `eu`) or an https host on techship.io or techship.ca, for\nexample `https://test-api-us.techship.io` (defaults to `us` for a new source; on an existing\none it can be sent alone to move the keys).\n"}, "secret_key": {"type": "string", "description": "TechShip only: the portal's secret key (`x-secret-key`), sent with `api_key`. Techdinamics\nissues one key set per portal. Tried before saving (by listing the portal's clients).\n"}, "username": {"type": "string", "description": "Snapfulfil only: the API user's name (set up in Snapfulfil for the API host and your Biz)."}, "password": {"type": "string", "format": "password", "description": "Snapfulfil only: the API user's password. Never returned."}, "connectors": {"type": "array", "items": {"type": "string"}, "description": "Supermetrics only: connector IDs to offer, for example `[\"AW\", \"FA\"]` (Google Ads, Facebook\nAds). Needed when the API key can't list the team's logins (no `ds_logins_read` scope).\nCan be sent without `api_key` to change the list and keep the saved key.\n"}, "label": {"type": "string", "description": "A display name", "for example \"Agency Google account\".": null}, "description": {"type": "string"}, "allowed": {"type": ["object", "null"], "description": "Which accounts and properties each service may reach through this data source. Maps a\nservice (ga4, ads, gtm, gsc, gbp, merchant, adsense, datamanager, meta_ads, callrail, microsoft_ads, bing_webmaster, supermetrics, salesforce, mntn, netsuite, hubspot, snapfulfil, techship, mailchimp, gorgias, klaviyo, ...) to a list of items written \"level:id\", for\nexample `{\"ga4\": [\"account:123\", \"property:456\"], \"ads\": [\"customer:1234567890\"]}`, or to\nnull for everything. Picking a parent (an account) includes everything under it. Replaces\nthe whole choice: services left out may reach everything. Leave the field out to keep the\ncurrent choice. List the choices with `list_data_source_scope_choices`.\n", "additionalProperties": {"type": ["array", "null"], "items": {"type": "string"}}}, "credentials": {"description": "A credential object (or its JSON as a string). Google takes a service account key\n(`type: service_account`) or an authorized user (`client_id`, `client_secret`,\n`refresh_token`). Shopify takes `shop` and `access_token`, or `shop`, `client_id` and\n`client_secret`. Meta takes `type: meta_system_user` (or `meta_user` with `expires_at`),\n`access_token`, `app_id` and `app_secret`. CallRail takes `type: callrail_api_key` and\n`api_key` (or send `api_key` on its own); limit it with `allowed`, for example\n`{\"callrail\": [\"company:COM123\"]}` (levels `account` > `company`). Bing Webmaster Tools\ntakes `type: bing_webmaster_api_key` and `api_key` (or send `api_key` on its own); limit\nit by site, for example `{\"bing_webmaster\": [\"site:https://www.example.com/\"]}`. Microsoft\nAdvertising connects with `start_data_source_sign_in` (`type: microsoft_ads`); limit it\nwith levels `customer` > `account`, for example `{\"microsoft_ads\": [\"account:123456\"]}`.\nSupermetrics takes `type: supermetrics_api_key`, `api_key` and optional `connectors` (or\nsend `api_key` on its own); limit it with levels `connector` > `account`, accounts written\n`<connector>/<account id>`, for example `{\"supermetrics\": [\"connector:FA\",\n\"account:AW/1234567890\"]}`. Salesforce connects with `start_data_source_sign_in` (`type:\nsalesforce`); it sees what the signed-in Salesforce user can, and can be limited to objects\n(level `object`), for example `{\"salesforce\": [\"object:Lead\", \"object:Opportunity\"]}`.\nWhile limited, queries, searches and record changes must stay inside those objects (lookups\nincluded) and reports and dashboards are off. MNTN takes `type: mntn_api_key` and `api_key`\n(or send `api_key` on its own); limit it with levels `advertiser` > `campaign`, for example\n`{\"mntn\": [\"campaign:12345\"]}`. While limited, its tools must name the advertiser or the\ncampaigns they read.\nRingCentral connects with `start_data_source_sign_in` (`type: ringcentral`); limit RingEX\nto extensions (users, call queues), for example `{\"ringex\": [\"extension:62912345008\"]}` (its\ntools must then name an allowed extension), and RingCX with levels `account` > `campaign`\nand `queue`, for example `{\"ringcx\": [\"campaign:1234\", \"queue:5678\"]}`.\nNetSuite takes `type: netsuite_m2m` with `account_id`, `client_id`,\n`certificate_id` and your own `private_key` (EC or RSA PEM) if you bring a key, or send the\nNetSuite fields above instead; limit it with level `subsidiary` (OneWorld), for example\n`{\"netsuite\": [\"subsidiary:3\"]}` (a parent includes its children). While limited, its tools\nmust name a `subsidiary_id`, and SuiteQL, items and campaigns are off.\nHubSpot connects with `start_data_source_sign_in` (`type: hubspot`), or takes a private\napp's access token as `api_key` (`pat-...`; no live events then); limit it with level `object`:\nobject types (`contacts`, `companies`, `deals`, `tickets`, a custom object's `2-1234567`) and\nmarketing tools (`forms`, `marketing_emails`, `campaigns`, `lists`), for example\n`{\"hubspot\": [\"object:contacts\", \"object:deals\", \"object:forms\"]}`.\nKlaviyo connects with `start_data_source_sign_in` (`type: klaviyo`), or takes a private API\nkey as `api_key` (`pk_...`); limit it with level `data`: kinds of data (`reports`, `campaigns`,\n`flows`, `audiences`, `profiles`, `events`, `forms`, `templates`), for example\n`{\"klaviyo\": [\"data:reports\", \"data:campaigns\"]}` reads results without anyone's personal data.\nSnapfulfil takes `type: snapfulfil_basic`, `api_url`, `username` and `password` (or send\nthose three on their own); limit it with levels `biz` > `owner`, for example\n`{\"snapfulfil\": [\"biz:BIZ1\"]}`; results are filtered on their Biz and stock owner, and write\ntools check the record's Biz. TechShip takes `type: techship_api_key`, `api_url`, `api_key`\nand `secret_key` (or send them on their own); limit it to TechShip clients (level `client`),\nfor example `{\"techship\": [\"client:ACME\"]}`. While limited, reports must name an allowed\nclient, and manifests (which span clients) can't be read or finalized.\nMailchimp connects with `start_data_source_sign_in` (`type: mailchimp`), or takes an API key as\n`api_key`; limit it to audiences (level `audience`), for example\n`{\"mailchimp\": [\"audience:abc123def4\"]}`. While limited, tools that name an audience, a\ncampaign or a store must stay inside it, list tools show only its rows, and live events are\nregistered only on those audiences.\nGorgias connects with `start_data_source_sign_in` (`type: gorgias`, `domain`), or takes\n`type: gorgias_api_key`, `domain`, `email` and `api_key` (or send those on their own); limit\nit to data areas (level `area`: `tickets`, `customers`, `satisfaction`, `statistics`,\n`voice_calls`), for example `{\"gorgias\": [\"area:customers\", \"area:statistics\"]}`. Live events\nneed `tickets`.\n", "oneOf": [{"type": "object", "additionalProperties": true}, {"type": "string"}]}}}}}}, "responses": {"200": {"description": "The data source", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DataSource"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_data_source", "x-handler": "delete_data_source", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Delete a data source", "description": "Deletes the data source and its credentials. Refused while a connection still uses it.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/data-sources/{source}/sign-in": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "start_data_source_sign_in", "x-handler": "data_source_sign_in", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Start a Google, Facebook, Microsoft, Salesforce, RingCentral or Mailchimp sign-in, or a Shopify, HubSpot, Gorgias or Klaviyo install, for a data source", "description": "Returns a link. Open it in a browser and sign in with the Google, Facebook, Microsoft, Salesforce or RingCentral account, or\napprove the app in the Shopify store. A Facebook sign-in asks which businesses, Pages, Instagram\naccounts and ad accounts to share. A Microsoft sign-in (Microsoft Advertising) must be an account with\na Microsoft Advertising user; it reaches every customer and ad account that user can, so limit it with\n`allowed` afterwards. A Salesforce sign-in connects one org (`login`: production, sandbox, or my_domain\nwith `my_domain`) as the user who signs in; that user needs API access, and a Salesforce admin of the\norg must have installed our package once (the reply's `install_url`). A RingCentral sign-in\n(`type: ringcentral`) should be an admin of the client's RingCentral account (the company call log,\nanalytics and RingCX need it); one sign-in serves both `ringex` (phone system) and `ringcx` (contact\ncenter), and Metric Sensei refreshes it every 15 minutes. A HubSpot link installs our app in the\nHubSpot account picked when it is opened (someone who can install apps there approves); one data source\nis one HubSpot account. A Mailchimp sign-in (`type: mailchimp`) connects the Mailchimp account picked when\nit is opened; Mailchimp's sign-ins don't expire (they stop when someone removes Metric Sensei under\nMailchimp > Profile > Integrations).\nA Gorgias link (`type: gorgias` with `domain`, the account name) opens that account's approve screen for\nour app; an admin of the account approves, and one data source is one Gorgias account. A Klaviyo link\ninstalls our app in the Klaviyo account its owner, admin or manager approves; one data source is one Klaviyo\naccount. When you finish,\nthe data source is created (or its credentials replaced).\nThe link works once and expires.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"type": {"type": "string", "enum": ["google", "shopify", "meta", "microsoft_ads", "salesforce", "ringcentral", "hubspot", "mailchimp", "gorgias", "klaviyo"], "description": "Needed for a new data source. WordPress sites connect from the plugin (approve_wordpress_connect) or with save_data_source; CallRail and Bing Webmaster Tools with an api_key in save_data_source."}, "label": {"type": "string"}, "description": {"type": "string"}, "uses": {"type": "array", "items": {"type": "string", "enum": ["ga4", "gtm", "gsc", "gbp", "merchant", "ads", "adsense", "datamanager", "meta_ads", "facebook", "instagram", "meta_business"]}, "description": "Google and Meta. The services to ask permission for (default ga4 and gtm for Google, all four for Meta)."}, "write": {"type": "boolean", "default": false, "description": "Also ask for permission to make changes (Google, Meta, Shopify; Microsoft and Salesforce have one permission for reading and changing)."}, "shop": {"type": "string", "description": "Shopify only. The store, for example my-store.myshopify.com"}, "domain": {"type": "string", "description": "Gorgias only. The account name, the acme in acme.gorgias.com (taken from an existing source when left out)."}, "login": {"type": "string", "enum": ["production", "sandbox", "my_domain"], "default": "production", "description": "Salesforce only. Where the org signs in: login.salesforce.com, test.salesforce.com or its My Domain."}, "my_domain": {"type": "string", "description": "Salesforce only, with login my_domain. The org's address, for example https://acme.my.salesforce.com"}, "tracking": {"type": "boolean", "default": false, "description": "Shopify only. Also ask for the tracking pixel's permissions (write_pixels,\nread_customer_events). When the install finishes, the store's tracked website is created\nor reused and the Metric Sensei pixel is turned on (see shopify-tracking).\n"}}}}}}, "responses": {"200": {"$ref": "#/components/responses/ConnectLink"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/app": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "install_source_app", "x-handler": "install_source_app", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Install the Metric Sensei app on a WordPress site, Shopify store, HubSpot, Gorgias or Klaviyo account or Salesforce org", "description": "Returns a link (`url`) for the platform's admin to open. WordPress (`site_url`): the site's own Authorize\nApplication screen; after Approve, Metric Sensei saves the data source, installs and activates the Metric\nSensei plugin (from WordPress.org), connects it and adds the tracking tag, and with `auto_update` (default\ntrue) switches WordPress's automatic updates for it on. If the site can't install plugins over its REST API,\nthe data source is still saved and the app's status offers the plugin download (`update_source_app` finishes\nit after the upload). Shopify (`shop`): the store's approve screen for our app (same as\nstart_data_source_sign_in). HubSpot: the account's install link for our app (same as start_data_source_sign_in\nwith type hubspot). Gorgias (`domain`): the account's approve screen for our app (same as\nstart_data_source_sign_in with type gorgias). Klaviyo: the account's authorize link for our app (same as\nstart_data_source_sign_in with type klaviyo). Salesforce (`login`): the install link of our package for a Salesforce admin;\nsign in afterwards with start_data_source_sign_in. The link works once and expires in 30 minutes\n(WordPress) or an hour (Shopify).\n", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"type": {"type": "string", "enum": ["wordpress", "shopify", "hubspot", "salesforce", "gorgias", "klaviyo"], "description": "Needed for a new data source."}, "domain": {"type": "string", "description": "Gorgias only. The account name, the acme in acme.gorgias.com."}, "site_url": {"type": "string", "description": "WordPress only. The site's address, for example https://www.example.com"}, "auto_update": {"type": "boolean", "default": true, "description": "WordPress only. Let WordPress update the plugin automatically."}, "write": {"type": "boolean", "default": false, "description": "WordPress and Shopify. AI tools may also make changes."}, "label": {"type": "string"}, "shop": {"type": "string", "description": "Shopify only. The store, for example my-store.myshopify.com"}, "tracking": {"type": "boolean", "default": false, "description": "Shopify only. Also turn on the tracking pixel."}, "login": {"type": "string", "enum": ["production", "sandbox"], "default": "production", "description": "Salesforce only."}}}}}}, "responses": {"200": {"description": "The link to open", "content": {"application/json": {"schema": {"type": "object", "properties": {"type": {"type": "string"}, "url": {"type": "string"}, "expires_at": {"type": "integer"}, "next": {"type": "string", "enum": ["approve", "sign_in"], "description": "What happens after the link: approve (Metric Sensei finishes by itself) or sign_in (sign in with Salesforce next)."}, "message": {"type": "string"}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "get": {"operationId": "get_source_app", "x-handler": "get_source_app", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Whether a data source's Metric Sensei app is installed and up to date", "description": "Asks the platform live. `state`: installed, update_available, needs_approval (Shopify: the store hasn't\napproved permissions the source uses; HubSpot, Gorgias and Klaviyo: the account hasn't approved a required scope), needs_update (WordPress plugin too old to connect from here),\nnot_installed, inactive, not_connected, updating (Salesforce is installing a new version) or unknown.\n`actions` lists what update_source_app can do now. WordPress also reports `version`, `latest_version`,\n`auto_update`, `download_url` and `upload_url`; Salesforce `version`, `latest_version` and `install_url`.\nShopify and HubSpot update their apps by themselves; Gorgias and Klaviyo run ours on their side (nothing to\nupdate).\n", "responses": {"200": {"description": "The app's status", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"source": {"type": "string"}, "type": {"type": "string"}, "state": {"type": "string"}, "message": {"type": "string"}, "version": {"type": ["string", "null"]}, "latest_version": {"type": ["string", "null"]}, "auto_update": {"type": ["boolean", "null"]}, "actions": {"type": "array", "items": {"type": "string", "enum": ["update", "auto_update_on", "auto_update_off"]}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/app/update": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "update_source_app", "x-handler": "update_source_app", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Bring a data source's Metric Sensei app up to date, or switch WordPress automatic updates", "description": "`action` update (default): WordPress installs or activates the plugin and connects it as needed (\"Finish\nsetup\"); Shopify, HubSpot, Gorgias and Klaviyo return a `url` to approve permissions the store or account is missing (or to\ninstall the app again); Salesforce asks the org to\ninstall our latest package version with the data source's own sign-in, or returns the install `url` for a\nSalesforce admin when that user may not install packages. `auto_update_on` / `auto_update_off`: WordPress\nonly.\n", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"action": {"type": "string", "enum": ["update", "auto_update_on", "auto_update_off"], "default": "update"}, "auto_update": {"type": "boolean", "description": "WordPress, with action update: also switch automatic updates."}}}}}}, "responses": {"200": {"description": "What happened", "content": {"application/json": {"schema": {"type": "object", "properties": {"ok": {"type": "boolean"}, "state": {"type": "string"}, "url": {"type": "string", "description": "A link to open when the platform needs a person to approve or install."}, "message": {"type": "string"}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/shopify-tracking": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "put": {"operationId": "shopify_tracking_on", "x-handler": "shopify_tracking_on", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Turn on tracking for a Shopify store", "description": "Creates the tracked website for the store's main domain (or reuses the client's existing one) and\nturns on the Metric Sensei pixel in the store, pointed at that website. Calling it again updates the\npixel's settings. The store must be connected with Connect to Shopify, with tracking permissions.\n`status` is `on`, or why tracking isn't on yet: `claimed_elsewhere` (another account tracks the\ndomain), `unavailable` (tracking isn't set up in this environment), `service_off` (turn on the\nAttribution service for the client first) or `missing_permission` (connect the store again with\n`tracking`). If the website later gets a custom tracking domain, the pixel switches to it by itself.\n", "responses": {"200": {"$ref": "#/components/responses/ShopifyTracking"}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "shopify_tracking_off", "x-handler": "shopify_tracking_off", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Turn off tracking for a Shopify store", "description": "Removes the Metric Sensei pixel from the store. The tracked website and its data stay.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/data-sources/{source}/blazeo": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "get": {"operationId": "get_blazeo_webhook", "x-handler": "blazeo_webhook_info", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Whether Blazeo is sending leads to a Blazeo data source", "description": "Blazeo (formerly ApexChat) has no API: it sends each chat lead to the data source's webhook URL.\n`status` is `receiving`, `waiting_for_first_lead` or `not_set_up`; also the number of leads received,\nwhen the last one arrived, test leads, and the field names to give Blazeo. The URL itself is secret and\nis shown only when it is made (`save_data_source`, `rotate_blazeo_webhook`).\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/blazeo/rotate": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "rotate_blazeo_webhook", "x-handler": "rotate_blazeo_webhook", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Make a new webhook URL for a Blazeo data source", "description": "Returns a new `webhook_url` (shown only once) with the method, format and field names. The old URL\nstops working at once, so send the new one to Blazeo support straight away. Stored leads stay.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/blazeo/test": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "send_blazeo_test_lead", "x-handler": "send_blazeo_test_lead", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Store a test lead in a Blazeo data source", "description": "Stores a lead marked as a test, as if Blazeo had sent one, to check the connection end to end with\nthe `blazeo_*` tools. Tools leave test leads out unless asked (`include_tests`).\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/netsuite": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "get": {"operationId": "get_netsuite_setup", "x-handler": "netsuite_setup", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Where a NetSuite data source's setup stands, with its certificates", "description": "Returns `step` (`upload_certificate`: upload `certificate_pem` in NetSuite and save the data source with\nthe `certificate_id` NetSuite shows; `renewing`: a new certificate, `new_certificate_pem`, waits for its\nID while the current one keeps working; `done`), `account_id`, `client_id`, `certificate_id`,\n`certificate_expires_at`, `certificate_days_left` and `where` (the NetSuite page). Certificates are\npublic; private keys are never returned.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/netsuite/certificate": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "new_netsuite_certificate", "x-handler": "new_netsuite_certificate", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Make a new certificate for a NetSuite data source", "description": "Makes a new key pair (EC P-256, valid 729 days; NetSuite allows two years at most). It waits next to the\ncurrent one, which keeps working, until you save the data source with the new `certificate_id`. Returns\nthe same as `GET .../netsuite`.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/netsuite/script": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "get": {"operationId": "get_netsuite_script", "x-handler": "netsuite_script", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Our NetSuite SuiteScript, with this data source's live-events address built in", "description": "Returns `script` (SuiteScript 2.1 user event script) and `filename`. Upload it in NetSuite >\nCustomization > Scripting > Scripts > New and deploy it on customer, lead, prospect, contact, sales\norder, invoice and cash sale records. It posts a short summary of each created or edited record to the\ndata source's live-events address (keep it private) and never changes anything in NetSuite. Live\nevents must be on for the data source (400 otherwise).\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/live-events": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "get": {"operationId": "get_live_events", "x-handler": "live_events_view", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "A data source's live events (webhooks) setting and status", "description": "Live events are what a platform pushes to Metric Sensei by webhook: Shopify orders, Facebook lead ads\nand comments, Instagram comments, CallRail calls and texts, WordPress form submissions and WooCommerce\norders, Business Profile reviews, Merchant Center product status changes, Google Ads lead form leads,\nMNTN verified visits, NetSuite customers, leads, orders and invoices (through our SuiteScript).\nReturns `supported`, `enabled`, `status` (`ok`, `partial`, `error`, `needs_setup`, `syncing`, `off`,\n`unsupported`), `message`, the `topics` (each with `on`), `subscriptions` made at the platform,\n`manual` setup steps (e.g. the URL and key to paste into a Google Ads lead form), `last_event_at` and\n`event_count`. Live events are on by default for sources that support them.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "update_live_events", "x-handler": "live_events_update", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Switch live events on or off and pick topics", "description": "Metric Sensei registers (or removes) the webhooks at the platform through its API, in the background\n(`status: syncing` until done). Leave a field out to keep it.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"enabled": {"type": "boolean"}, "topics": {"type": "array", "items": {"type": "string"}, "description": "Topic IDs from get_live_events."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/setup-checks": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_setup_checks", "x-handler": "tenant_setup_checks", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "A client's setup checks and feature readiness", "description": "Setup checks read the settings and permissions on each platform's side that Metric Sensei features need\n(for example GA4 data retention and User-ID, Google Ads auto-tagging, Search Console access, Meta\npermissions and pixels, the Shopify pixel, the WordPress plugin, CallRail session tracking). They run when\na data source is connected, once a day, and on `run_data_source_setup_checks`, and never change anything.\n\n`sources`: each checked data source with `status` (`blocking`, `warning`, `ok`), `counts`, `checked_at`\nand `results`. Each result has `check`, `title`, `status` (`ok`, `warning`, `blocking`, `unknown` =\ncouldn't tell), `detail`, `target` (the property or account checked), `features` (what won't work until\nit's fixed) and, for problems, `fix` (numbered steps) and `help_url`.\n`readiness`: per feature, the worst status across the client's data sources and which checks block it.\nOnly features of services the client has on are listed.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/setup-checks": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "get": {"operationId": "get_data_source_setup_checks", "x-handler": "source_setup_checks", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "One data source's setup checks", "description": "The saved results of one data source's setup checks, problems first (see `list_setup_checks`).", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/setup-checks/run": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "run_data_source_setup_checks", "x-handler": "run_setup_checks", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Check a data source's setup now", "description": "Reads the platform's settings again now (a few read-only calls; this also runs daily) and returns the results. Use it after fixing something to confirm the fix.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/live-events/sync": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "sync_live_events", "x-handler": "live_events_sync", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Check a data source's webhooks now", "description": "Checks the webhooks at the platform and makes missing ones again (this also runs daily).", "responses": {"200": {"$ref": "#/components/responses/Object"}, "202": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/live-events/test": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "post": {"operationId": "send_live_event_test", "x-handler": "live_events_test", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Store a test live event", "description": "Stores a sample event marked as a test; lists and tools leave tests out unless asked.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/live-events/events": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}], "get": {"operationId": "list_live_events", "x-handler": "list_live_events", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Events a data source received by webhook", "description": "Newest first, without payloads (get_live_event has the payload). Kept for 25 months. Page with\n`cursor` (the `next_cursor` of the previous page).\n", "parameters": [{"name": "start", "in": "query", "description": "YYYY-MM-DD or ISO 8601 (default: 30 days ago).", "schema": {"type": "string"}}, {"name": "end", "in": "query", "description": "YYYY-MM-DD or ISO 8601 (default: now).", "schema": {"type": "string"}}, {"name": "event_type", "in": "query", "description": "e.g. orders/create, leadgen, post_call.", "schema": {"type": "string"}}, {"name": "service", "in": "query", "description": "e.g. shopify, facebook, instagram, gbp.", "schema": {"type": "string"}}, {"name": "include_tests", "in": "query", "schema": {"type": "boolean"}}, {"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/live-events/events/{event_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}, {"name": "event_id", "in": "path", "required": true, "schema": {"type": "string"}}], "get": {"operationId": "get_live_event", "x-handler": "get_live_event", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "One received event with its payload", "description": "The platform's payload as received, plus details fetched afterwards (e.g. a lead's answers).", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/wordpress/approve": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "approve_wordpress_connect", "x-handler": "approve_wordpress", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Approve connecting a WordPress site", "description": "Step 1 of the Metric Sensei WordPress plugin's Connect. The plugin opens\n`/connect/wordpress?site=&callback=&state=&challenge=` on the admin page; pass those four values here\nto link the site to this client. Returns `redirect_url`: open it in the browser that came from the\nplugin (it goes back to the site's wp-admin with a single-use code, valid 10 minutes). The plugin then\nfinishes on its own: the site becomes a `wordpress` data source with a connection, and a tracked\nwebsite whose tag the plugin adds to every page.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["site", "callback", "state", "challenge"], "properties": {"site": {"type": "string", "description": "The site's address, e.g. https://acme.com"}, "callback": {"type": "string", "description": "The plugin's return page (the site's own wp-admin, https)"}, "state": {"type": "string", "description": "The plugin's state value from its link."}, "challenge": {"type": "string", "description": "The plugin's PKCE S256 challenge from its link."}, "write": {"type": "boolean", "default": false, "description": "Let AI apps make changes on the site."}}}}}}, "responses": {"200": {"description": "Where to send the browser", "content": {"application/json": {"schema": {"type": "object", "properties": {"redirect_url": {"type": "string"}, "expires_at": {"type": "integer", "description": "Unix time the code expires."}, "site": {"type": "string"}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/connections": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_connections", "x-handler": "list_connections", "x-list": {"key": "connections"}, "x-roles": ["admin", "tenant_admin", "service_user"], "tags": ["Connections"], "summary": "List a client's connections", "responses": {"200": {"description": "Connections (never with credentials)", "content": {"application/json": {"schema": {"type": "object", "properties": {"connections": {"type": "array", "items": {"$ref": "#/components/schemas/Connection"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/clients/{client}/connections/{service}/{name}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/service"}, {"$ref": "#/components/parameters/connection_name"}], "put": {"operationId": "save_connection", "x-handler": "put_connection", "x-roles": ["admin", "tenant_admin", "service_user"], "tags": ["Connections"], "summary": "Add or change a connection", "description": "Creates the connection if it doesn't exist, otherwise changes only the fields you send. A\nconnection uses one of the client's data sources for its credentials: a new one needs\n`data_source` (a Google source for the Google services, a Shopify source for Shopify). PageSpeed\nInsights needs no data source.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"data_source": {"type": "string", "description": "The name of the data source to use."}, "default": {"type": "boolean", "description": "Make this the client's default connection for the service."}, "allow_write": {"type": "boolean", "description": "Allow tools that change data."}, "description": {"type": "string"}, "settings": {"type": "object", "additionalProperties": {"type": "string"}, "description": "Service settings", "for example default_property_id.": null}, "replace_settings": {"type": "boolean", "default": false, "description": "Replace all settings instead of merging."}}}}}}, "responses": {"200": {"description": "The connection", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Connection"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_connection", "x-handler": "delete_connection", "x-roles": ["admin", "tenant_admin", "service_user"], "tags": ["Connections"], "summary": "Delete a connection", "description": "Deletes the connection right away. Its data source stays.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/connections/{service}/{name}/options/{setting}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/service"}, {"$ref": "#/components/parameters/connection_name"}, {"name": "setting", "in": "path", "required": true, "description": "The setting to list choices for, for example default_property_id.", "schema": {"type": "string"}}], "get": {"operationId": "list_setting_choices", "x-handler": "setting_options", "x-roles": ["admin", "tenant_admin", "service_user"], "tags": ["Connections"], "summary": "List the choices for a connection setting", "description": "Fetches the values a setting can take (for example the GA4 properties or GTM containers the\nconnection can reach), using the connection's own credentials. Settings a list depends on can\nbe passed as query parameters; any missing ones come back in `needs`.\n", "parameters": [{"name": "values", "in": "query", "style": "form", "explode": true, "description": "Unsaved values of other settings, for example account_id=123.", "schema": {"type": "object", "additionalProperties": {"type": "string"}}}], "responses": {"200": {"description": "Choices", "content": {"application/json": {"schema": {"type": "object", "properties": {"options": {"type": "array", "items": {"type": "object", "additionalProperties": true}}, "needs": {"type": "array", "items": {"type": "string"}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/scope-options/{service}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}, {"$ref": "#/components/parameters/service"}], "get": {"operationId": "list_data_source_scope_choices", "x-handler": "source_scope_options", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "List the accounts and properties a data source can be limited to", "description": "Everything the data source's sign-in can see for one service, as a tree (for example GA4 accounts\nand their properties, Google Ads manager accounts and the accounts under them, GTM accounts and\ncontainers, Search Console sites, Business Profile accounts and locations, Merchant Center\naccounts, AdSense accounts, Meta business portfolios with their ad accounts, Pages or Instagram accounts), plus the\ncurrent choice. Save a choice with `save_data_source` (`allowed`). Shopify\ndata sources can't be limited: each one is already a single store.\n", "responses": {"200": {"description": "The levels, items and current choice", "content": {"application/json": {"schema": {"type": "object", "properties": {"service": {"type": "string"}, "title": {"type": "string"}, "levels": {"type": "array", "description": "Top level first", "items": {"type": "object", "properties": {"key": {"type": "string"}, "title": {"type": "string"}}}}, "items": {"type": "array", "items": {"type": "object", "properties": {"level": {"type": "string"}, "id": {"type": "string"}, "label": {"type": "string"}, "parent": {"type": "string", "description": "The parent item as \"level:id\", or empty at the top"}}}}, "selected": {"type": ["array", "null"], "items": {"type": "string"}, "description": "The current choice as \"level:id\" items, or null when the service may reach everything"}, "error": {"type": "string", "description": "Why the list couldn't be loaded (for example a missing permission)", "or empty": null}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/data-sources/{source}/options/{service}/{setting}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/source"}, {"$ref": "#/components/parameters/service"}, {"name": "setting", "in": "path", "required": true, "description": "The setting to list choices for, for example default_property_id.", "schema": {"type": "string"}}], "get": {"operationId": "list_source_setting_choices", "x-handler": "source_setting_options", "x-roles": ["admin", "tenant_admin", "service_user"], "tags": ["Connections"], "summary": "List the choices for a setting before adding a connection", "description": "Like listing a connection setting's choices, but for a connection you haven't added yet: fetches\nthe values a service's setting can take (for example the GA4 properties) with a data source's\ncredentials. The data source must be of the kind the service uses. Use it to fill in the settings\nof a new connection, then add the connection with PUT /clients/{client}/connections/{service}/{name}.\nSettings a list depends on can be passed as query parameters; any missing ones come back in `needs`.\n", "parameters": [{"name": "values", "in": "query", "style": "form", "explode": true, "description": "Values of other settings chosen so far, for example account_id=123.", "schema": {"type": "object", "additionalProperties": {"type": "string"}}}], "responses": {"200": {"description": "Choices", "content": {"application/json": {"schema": {"type": "object", "properties": {"options": {"type": "array", "items": {"type": "object", "additionalProperties": true}}, "needs": {"type": "array", "items": {"type": "string"}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/users": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_client_users", "x-handler": "list_users", "x-list": {"key": "users"}, "x-roles": ["admin", "tenant_admin"], "tags": ["Client users"], "summary": "List a client's users", "responses": {"200": {"description": "Users", "content": {"application/json": {"schema": {"type": "object", "properties": {"users": {"type": "array", "items": {"$ref": "#/components/schemas/Person"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}, "post": {"operationId": "invite_client_user", "x-handler": "invite_user", "x-roles": ["admin", "tenant_admin"], "tags": ["Client users"], "summary": "Invite someone to a client", "description": "Creates their account if needed and emails them an invitation. Existing accounts are added to the client. Client admins can invite people to the clients they manage.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["email"], "properties": {"email": {"type": "string", "format": "email"}, "manager": {"type": "boolean", "default": false, "description": "Make them a client admin."}}}}}}, "responses": {"201": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/users/{username}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/username"}], "delete": {"operationId": "remove_client_user", "x-handler": "remove_user", "x-roles": ["admin", "tenant_admin"], "tags": ["Client users"], "summary": "Remove someone from a client", "description": "Their account stays; they lose this client, and their keys and connected apps here stop working.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/users/{username}/access": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/username"}], "get": {"operationId": "get_user_access", "x-handler": "get_user_access", "x-roles": ["admin", "tenant_admin"], "tags": ["Client users"], "summary": "Show a user's access", "description": "Two separate kinds of access. `grants`: which connections the user can use through the API/MCP Gateway\nand their API/MCP Gateway keys. `services`: what the user can see (read) or change (write) per service in\nthe admin and through this API. Neither implies the other.\n", "responses": {"200": {"description": "The user's grants, admin service access, and what there is to grant", "content": {"application/json": {"schema": {"type": "object", "properties": {"username": {"type": "string"}, "grants": {"$ref": "#/components/schemas/Grants"}, "services": {"$ref": "#/components/schemas/ServiceAccess"}, "connections": {"type": "array", "items": {"$ref": "#/components/schemas/Connection"}}, "all_services": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "title": {"type": "string"}}}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "set_user_access", "x-handler": "put_user_access", "x-roles": ["admin", "tenant_admin"], "tags": ["Client users"], "summary": "Set a user's access", "description": "Replaces the user's connection grants and, when `services` is sent, their admin service access\n(leave `services` out to keep it as it is). Leave a connection or service out (or set it to none)\nto take access away. With `services` read, the user sees that service's connections in the admin;\nwith write they can also add, change and remove them (but not turn API/MCP Gateway changes on or off).\nClient admins always have full access.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"grants": {"$ref": "#/components/schemas/Grants"}, "services": {"$ref": "#/components/schemas/ServiceAccess"}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/connected-apps": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_connected_apps", "x-handler": "list_sessions", "x-path": "/clients/{client}/sessions", "x-list": {"key": "sessions", "as": "connected_apps"}, "x-roles": ["admin", "tenant_admin"], "tags": ["Connected apps"], "summary": "List apps signed in to a client", "responses": {"200": {"description": "Connected apps", "content": {"application/json": {"schema": {"type": "object", "properties": {"connected_apps": {"type": "array", "items": {"$ref": "#/components/schemas/ConnectedApp"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/clients/{client}/connected-apps/{session_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/session_id"}], "delete": {"operationId": "sign_out_connected_app", "x-handler": "revoke_session", "x-path": "/clients/{client}/sessions/{session_id}", "x-roles": ["admin", "tenant_admin"], "tags": ["Connected apps"], "summary": "Sign out an app from a client", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/identity": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_cliententity", "x-handler": "identity_view", "x-roles": ["admin", "tenant_admin"], "tags": ["Identity"], "summary": "Show a client's sign-in setup", "description": "Company sign-in providers, email domains (with their DNS verification records) and social sign-in choices.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/identity/providers": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "add_sign_in_provider", "x-handler": "add_idp", "x-roles": ["admin", "tenant_admin"], "tags": ["Identity"], "summary": "Add a company sign-in provider (SSO)", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProviderInput"}}}}, "responses": {"201": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/identity/providers/{provider}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/provider"}], "put": {"operationId": "update_sign_in_provider", "x-handler": "update_idp", "x-roles": ["admin", "tenant_admin"], "tags": ["Identity"], "summary": "Change a company sign-in provider", "description": "Send only what changes. A blank client_secret keeps the current one.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProviderInput"}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_sign_in_provider", "x-handler": "delete_idp", "x-roles": ["admin", "tenant_admin"], "tags": ["Identity"], "summary": "Remove a company sign-in provider", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/identity/domains": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "add_email_domain", "x-handler": "add_domain", "x-roles": ["admin", "tenant_admin"], "tags": ["Identity"], "summary": "Claim an email domain for the client", "description": "Returns a DNS TXT record to add. People with that domain are sent to the client's sign-in once it's verified.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["domain"], "properties": {"domain": {"type": "string", "example": "example.com"}}}}}}, "responses": {"201": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/identity/domains/{domain}/verify": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/domain"}], "post": {"operationId": "verify_email_domain", "x-handler": "verify_domain", "x-roles": ["admin", "tenant_admin"], "tags": ["Identity"], "summary": "Check a domain's DNS record", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/identity/domains/{domain}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/domain"}], "delete": {"operationId": "delete_email_domain", "x-handler": "delete_domain", "x-roles": ["admin", "tenant_admin"], "tags": ["Identity"], "summary": "Release an email domain", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/identity/social/{provider}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/social_provider"}], "put": {"operationId": "set_client_social_sign_in", "x-handler": "set_social", "x-roles": ["admin", "tenant_admin"], "tags": ["Identity"], "summary": "Allow or block a social sign-in for a client", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["accept"], "properties": {"accept": {"type": "boolean"}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies": {"get": {"operationId": "list_agencies", "x-handler": "list_agencies", "x-list": {"key": "agencies"}, "x-roles": ["admin", "agency_admin"], "tags": ["Agencies"], "summary": "List agencies", "description": "Global admins see every agency. Agency admins see the agencies they manage.", "responses": {"200": {"description": "Agencies", "content": {"application/json": {"schema": {"type": "object", "properties": {"agencies": {"type": "array", "items": {"$ref": "#/components/schemas/Agency"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/agencies/{agency_id}": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "get_agency", "x-handler": "get_agency", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies"], "summary": "Get an agency and its clients", "responses": {"200": {"description": "The agency", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Agency"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/plans": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "list_billing_plans", "x-handler": "agency_plans", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies", "Billing"], "summary": "The billing plans you may put the agency on", "description": "The agency's plan this month (`plan`), any switch waiting for next month (`next_plan`) and the\nplans you may pick. Agency admins see the plans agencies can choose; some plans can only be\nassigned by a Metric Sensei admin and aren't listed. `can_change` is false while the agency is on\nsuch a plan. `changes_start` says when a change you make applies: `next_month` for agency admins.\n", "responses": {"200": {"description": "Plans", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgencyPlans"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/plan": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "put": {"operationId": "set_billing_plan", "x-handler": "set_agency_plan", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies", "Billing"], "summary": "Switch the agency's billing plan", "description": "Agency admins may pick a plan agencies can choose (see `list_billing_plans`). The switch starts on\nthe 1st of next month and the current plan applies until then; picking the current plan cancels a\nplanned switch. An agency on a plan only a Metric Sensei admin can assign can't switch itself (403).\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["plan"], "properties": {"plan": {"type": "string", "example": "standard"}}}}}}, "responses": {"200": {"description": "The agency's plan now, any planned switch, and the month the change starts", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgencyPlan"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/upgrade": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "post": {"operationId": "upgrade_agency", "x-handler": "upgrade_agency", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies"], "summary": "Upgrade a single-customer account to an agency account", "description": "A single-customer (solo) account becomes a normal agency that can add and manage more clients. Its\nclient's admins become admins of the agency, and the client stays as the agency's first client. An\naccount that is already an agency gets 400.\n", "responses": {"200": {"description": "The agency, now with `solo` false", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Agency"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/users": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "list_agency_users", "x-handler": "list_agency_users", "x-list": {"key": "users"}, "x-roles": ["admin", "agency_admin"], "tags": ["Agencies"], "summary": "List an agency's people", "description": "Agency admins (`manager`) manage every client the agency owns; members use them.", "responses": {"200": {"description": "People", "content": {"application/json": {"schema": {"type": "object", "properties": {"users": {"type": "array", "items": {"$ref": "#/components/schemas/AgencyUser"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}}}}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}, "post": {"operationId": "invite_agency_user", "x-handler": "invite_agency_user", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies"], "summary": "Invite someone to an agency", "description": "Creates the account if needed (emailing a temporary password) and adds the agency role.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["email"], "properties": {"email": {"type": "string", "format": "email"}, "manager": {"type": "boolean", "description": "Make them an agency admin."}}}}}}, "responses": {"201": {"description": "The person and their roles", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/users/{username}": {"parameters": [{"$ref": "#/components/parameters/agency_id"}, {"$ref": "#/components/parameters/username"}], "put": {"operationId": "update_agency_user", "x-handler": "set_agency_user", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies"], "summary": "Add someone to an agency, or change whether they're an agency admin", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["manager"], "properties": {"manager": {"type": "boolean"}}}}}}, "responses": {"200": {"description": "The person's roles", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "remove_agency_user", "x-handler": "remove_agency_user", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies"], "summary": "Remove someone from an agency", "description": "Their keys and connected apps in clients they can no longer reach are removed too.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/services/{service}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "service", "in": "path", "required": true, "schema": {"type": "string", "example": "attribution"}}], "put": {"operationId": "set_client_service", "x-handler": "set_tenant_service", "x-roles": ["admin", "agency_admin"], "tags": ["Clients", "Billing"], "summary": "Turn a paid service on or off for a client", "description": "Each service a client has on adds its monthly base fee (charged by the day) to the agency's invoice.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["on"], "properties": {"on": {"type": "boolean"}}}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/billing": {"get": {"operationId": "get_billing_overview", "x-handler": "billing_overview", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "Balances and invoices for every agency you can see", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/billing": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "get_agency_billing", "x-handler": "billing_summary", "x-path": "/billing/agencies/{agency_id}", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "An agency's balance, invoices and saved cards", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/billing/usage": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "get_agency_usage", "x-handler": "billing_usage", "x-path": "/billing/agencies/{agency_id}/usage", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "Usage and charges so far, by client", "description": "An estimate of the invoice for a month (this month by default), with each client's measured usage.\n", "parameters": [{"name": "period", "in": "query", "schema": {"type": "string", "example": "2026-11"}}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Invoice"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/invoices/{period}": {"parameters": [{"$ref": "#/components/parameters/agency_id"}, {"$ref": "#/components/parameters/period"}], "get": {"operationId": "get_invoice", "x-handler": "get_invoice", "x-path": "/billing/agencies/{agency_id}/invoices/{period}", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "Get an invoice with its lines by client and its payments", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Invoice"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/invoices/{period}/pay": {"parameters": [{"$ref": "#/components/parameters/agency_id"}, {"$ref": "#/components/parameters/period"}], "post": {"operationId": "pay_invoice", "x-handler": "charge_invoice", "x-path": "/billing/agencies/{agency_id}/invoices/{period}/charge", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "Charge the invoice's balance to the agency's default card now", "description": "Answers 402 if the card is declined.\n", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Invoice"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/card-form-token": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "get_card_form_token", "x-handler": "billing_client_token", "x-path": "/billing/agencies/{agency_id}/client-token", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "A one-time token for Braintree's card form", "description": "Use it with Braintree's client SDK (Hosted Fields or Drop-in) to collect a card; send the nonce it returns to Save a card. Card numbers never reach Metric Sensei.\n", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/cards": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "post": {"operationId": "add_card", "x-handler": "add_card", "x-path": "/billing/agencies/{agency_id}/cards", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "Save a card for automatic charges", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["nonce"], "properties": {"nonce": {"type": "string", "description": "From Braintree's card form."}, "default": {"type": "boolean", "default": true}}}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/cards/{token}/default": {"parameters": [{"$ref": "#/components/parameters/agency_id"}, {"$ref": "#/components/parameters/card_token"}], "put": {"operationId": "set_default_card", "x-handler": "default_card", "x-path": "/billing/agencies/{agency_id}/cards/{token}/default", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "Make a saved card the one that gets charged", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/cards/{token}": {"parameters": [{"$ref": "#/components/parameters/agency_id"}, {"$ref": "#/components/parameters/card_token"}], "delete": {"operationId": "delete_card", "x-handler": "delete_card", "x-path": "/billing/agencies/{agency_id}/cards/{token}", "x-roles": ["admin", "agency_admin"], "tags": ["Billing"], "summary": "Delete a saved card", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/invites/accept": {"post": {"operationId": "accept_invite", "x-handler": "accept_invite", "x-roles": ["everyone"], "tags": ["Invites"], "summary": "Accept an invite with your account", "description": "For people who already have an account. The invite's email must be yours.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["token"], "properties": {"token": {"type": "string", "description": "The code after `#` in the invite link."}, "form": {"allOf": [{"$ref": "#/components/schemas/Contact"}, {"type": "object", "properties": {"company": {"type": "string"}, "accept_terms": {"type": "boolean"}}}]}}}}}}, "responses": {"200": {"description": "Accepted", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/subscription": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "get_subscription", "x-handler": "agency_trial", "x-path": "/agencies/{agency_id}/trial", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies", "Billing"], "summary": "Free trial status and limits", "description": "`state` is `none` (no trial), `trial` (running), `limit` (a limit was reached; services are\npaused), `expired` (ended without a card; services are paused) or `converted` (paying).\n", "responses": {"200": {"description": "Trial", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "start_subscription", "x-handler": "subscribe_agency", "x-path": "/agencies/{agency_id}/subscribe", "x-roles": ["admin", "agency_admin"], "tags": ["Agencies", "Billing"], "summary": "End the free trial and start paying", "description": "Needs a saved card. Lifts the trial limits; billing counts from now.", "responses": {"200": {"description": "Trial", "content": {"application/json": {"schema": {"type": "object"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/pricing": {"get": {"operationId": "get_pricing", "x-handler": "get_pricing", "x-roles": ["admin", "agency_admin"], "tags": ["Pricing"], "summary": "Get the prices in force", "description": "Each service has a monthly base fee per agency that includes `included_clients` clients, a\n`client_fee` for each client above that, and per metric the units each client adds to the agency's\npooled allowance (`included`, also in `free_units`); usage above the pool is charged at `unit_price`.\n`tiers` raise the base fee and clients included and discount the rest; a standard plan's `tier`\napplies to every service (built-in plans Core, Growth and Scale). `platform` (storage and API\nrequests) has no base fee and covers every client with any service on.\n", "responses": {"200": {"description": "The price book", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PriceBook"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/tracked-sites": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_tracked_sites", "x-handler": "list_tracked_sites", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "A client's tracked sites (websites, apps and APIs)", "description": "Tracked sites are shared by services, the way data sources are. Each has a type (`kind`):\n`website` (gets a JavaScript tag), `ios_app`, `android_app` or `api`, the services turned on\nfor it, and each service's settings. Also lists the types and the services, with the types\neach service works with and whether it is on for the client.\n", "responses": {"200": {"description": "Sites, types and services", "content": {"application/json": {"schema": {"type": "object", "properties": {"sites": {"type": "array", "items": {"$ref": "#/components/schemas/TrackedSite"}}, "kinds": {"type": "array", "items": {"type": "object", "properties": {"key": {"type": "string"}, "label": {"type": "string"}}}}, "services": {"type": "array", "items": {"type": "object", "properties": {"key": {"type": "string", "example": "attribution"}, "label": {"type": "string"}, "kinds": {"type": "array", "items": {"type": "string"}}, "description": {"type": "string"}, "on_for_client": {"type": "boolean"}}}}, "tags_available": {"type": "boolean", "description": "False until website tags can be published in this environment"}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "create_tracked_site", "x-handler": "create_tracked_site", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Add a website, app or API to track", "description": "A website needs `domain` (one site per domain), an app needs `app_id` (the iOS bundle ID or\nAndroid package name; one site per app), and an API needs `name`. Services in `services` must\nwork with the type and be on for the client. A website's tag is published at once; the\nresponse has the snippet to paste into the website's <head>.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/TrackedSiteInput"}, {"type": "object", "required": ["kind"], "properties": {"kind": {"type": "string", "enum": ["website", "ios_app", "android_app", "api"]}}}]}}}}, "responses": {"201": {"description": "Created (and a website's tag published)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackedSite"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/tracked-sites/{site_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "site_id", "in": "path", "required": true, "schema": {"type": "string", "example": "MS-7KQ2M9XP"}}], "get": {"operationId": "get_tracked_site", "x-handler": "get_tracked_site", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "A tracked site, with the snippet for a website", "responses": {"200": {"description": "The site", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackedSite"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "patch": {"operationId": "update_tracked_site", "x-handler": "update_tracked_site", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Change a tracked site, its services or their settings", "description": "Send only the fields to change; the type can't change. `services` replaces the list of\nservices turned on. `settings` is merged per service (`{\"attribution\": {\"debug\": true}}`\nchanges only that setting), and a service's settings are kept while it is off. A website's\ntag is published again (live within 5 minutes) and loads only the services running on it.\n\nAnalytics settings (`settings.analytics`, ms-analytics): `timezone` (report days, e.g.\n\"America/Chicago\"), `retention_months` (1-25), `exclude_paths` ([\"/admin\"]), `goals`\n([{\"name\", \"type\": \"page\", \"path\"} | {\"name\", \"type\": \"event\", \"event\"}], up to 20; optional\n`ga4_event` names the matching GA4 key event for blended numbers, and event goals also match a GA4\nevent of the same name), `funnels`\n([{\"name\", \"steps\": [2-8 page/event steps; a path ending in * matches what's under it]}]),\n`ad_lift` (the Ad-driven lift report: {\"brand_terms\", \"exclude_terms\", \"outcomes\", \"ads_customer_id\",\n\"meta_ad_account_id\", \"mntn_advertiser_id\", \"ms_ads_account_id\", \"site_url\", \"flights\": [{\"begin\",\n\"end\", \"label\"}]}),\n`sources` (connected sources shown in the reports: {\"ga4\": {\"connection\", \"property_id\"}, \"gsc\":\n{\"connection\", \"site_url\"}, \"ads\": {\"connection\", \"customer_id\"}, \"meta_ads\": {\"connection\",\n\"ad_account_id\"}, \"microsoft_ads\": {\"connection\", \"account_id\"}, \"shopify\": {\"connection\"},\n\"callrail\": {\"connection\", \"account_id\", \"company_id\"}, \"mailchimp\": {\"connection\", \"list_id\"\n(optional)}, \"klaviyo\": {\"connection\"}, \"pagespeed\": {\"connection\"}, \"gorgias\": {\"connection\"} (the\nSupport panel), \"merchant\": {\"connection\", \"account_id\" (optional)} (the Shopping panel: Google Merchant\nCenter free listings, Shopping ads and products), \"adsense\": {\"connection\", \"account_id\"} (the AdSense\npanel: earnings on the site's domain), \"bing_webmaster\": {\"connection\", \"site_url\"}, \"gbp\": {\"connection\",\n\"location_id\" (optional)}, \"facebook\": {\"connection\", \"page_id\"}, \"instagram\": {\"connection\",\n\"ig_account_id\"}, \"mntn\": {\"connection\", \"advertiser_id\" (optional; TV spend also counts in return on ad\nspend)}, \"woocommerce\": {\"connection\"} (a WordPress connection), \"netsuite\": {\"connection\",\n\"subsidiary_id\" (optional)}, \"ringex\": {\"connection\", \"extension_ids\" (optional, comma-separated)}, \"blazeo\":\n{\"connection\"}, \"hubspot\": {\"connection\"}, \"salesforce\": {\"connection\"}}; linking GA4\nrecords you as `linked_by`, and its nightly copy runs with your access), `outbound`, `downloads`,\n`forms`, `scroll`, `hash_routes` (true/false), `not_found_title`, `analytics_version` and\n`forward_events` (true by default: the tag also sends every named event to the other trackers on\nthe client's page, such as GA4, Google Tag Manager and Meta Pixel, named `ms_<event>`, when the\nvisitor allows it and never with emails or phone numbers; false turns it off). The\nreports themselves are API/MCP Gateway tools (`analytics_*`); with GA4 linked, `analytics_report`,\n`analytics_breakdown`, `analytics_goals` and `analytics_standard_report` take `data` (ours, ga4 or\nblended; without it, the person's saved choice, else blended when GA4 is linked, else ours), and\n`analytics_audience` and `analytics_data_check` add GA4-only breakdowns and the ours-vs-GA4 check.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackedSiteInput"}}}}, "responses": {"200": {"description": "Saved (and a website's tag published)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackedSite"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_tracked_site", "x-handler": "delete_tracked_site", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Stop tracking a website, app or API", "description": "Removes the site, and a website's tag. Pages that still have the snippet load nothing.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/tracked-sites/{site_id}/domain": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "site_id", "in": "path", "required": true, "schema": {"type": "string", "example": "MS-7KQ2M9XP"}}], "get": {"operationId": "get_tracking_domain", "x-handler": "get_tracking_domain", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "A website's custom tracking domain and its status", "description": "A custom tracking domain (`t.acme.com`) serves the website's tag, its code and the ID endpoint from\nthe client's own domain instead of our CDN, and lets the ID endpoint keep its own cookie on the\nsite's domain. `available` is false until custom domains are set up in this environment.\n", "responses": {"200": {"description": "The domain (null when there is none)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingDomainAnswer"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "put_tracking_domain", "x-handler": "put_tracking_domain", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Start setting up a custom tracking domain", "description": "`host` must be directly under the website's domain (`t.acme.com` for acme.com) and not a host the\nwebsite runs on. The answer lists four NS records (`domain.records`) to add at the client's DNS\nprovider. We check for them every hour (or call `.../domain/check`); once they are in, CloudFront\ngets a certificate and the domain becomes `active`. One domain per website.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["host"], "properties": {"host": {"type": "string", "example": "t.acme.com"}}}}}}, "responses": {"201": {"description": "Waiting for the NS records", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingDomainAnswer"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_tracking_domain", "x-handler": "delete_tracking_domain", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Stop using the custom tracking domain", "description": "The tag moves back to our CDN first, so pages keep working. The NS records can then be removed.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/tracked-sites/{site_id}/domain/check": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "site_id", "in": "path", "required": true, "schema": {"type": "string", "example": "MS-7KQ2M9XP"}}], "post": {"operationId": "check_tracking_domain", "x-handler": "check_tracking_domain", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Check a custom tracking domain now", "description": "Looks for the NS records and the certificate now instead of waiting for the hourly check. When\nthe domain becomes `active` the website's tag is published again with every URL on it.\n", "responses": {"200": {"description": "The domain after the check", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingDomainAnswer"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_attribution", "x-handler": "attribution_overview", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "A client's tracked websites and tracking settings", "description": "Lists the sites (one per website domain) that run the Metric Sensei attribution script, the\nclient's settings, whether the Attribution service is on, and whether services are paused.\n", "responses": {"200": {"description": "Sites and settings", "content": {"application/json": {"schema": {"type": "object", "properties": {"service_on": {"type": "boolean"}, "paused": {"type": "boolean"}, "available": {"type": "boolean", "description": "False until attribution is set up in this environment"}, "cdn_url": {"type": "string"}, "settings": {"$ref": "#/components/schemas/TrackingSettings"}, "sites": {"type": "array", "items": {"$ref": "#/components/schemas/TrackingSite"}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/settings": {"parameters": [{"$ref": "#/components/parameters/client"}], "put": {"operationId": "update_attribution_settings", "x-handler": "put_attribution_settings", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Set how visitor IDs are shared and how long they're kept", "description": "`id_scope` client (default) gives a visitor the same IDs on all of this client's sites; domain\nkeeps them separate per site. Visitor records are deleted after `retention_months` without a\nvisit (default 3). `link_sources` (default true) links data sources' records to visitors.\nSettings left out keep their current value. Every site is published again.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingSettings"}}}}, "responses": {"200": {"description": "Saved", "content": {"application/json": {"schema": {"type": "object", "properties": {"settings": {"$ref": "#/components/schemas/TrackingSettings"}, "republished": {"type": "integer"}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_privacy_requests", "x-handler": "list_privacy_requests", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "List the client's visitor privacy requests, newest first", "responses": {"200": {"description": "Requests, with the kinds and identity-check methods", "content": {"application/json": {"schema": {"type": "object", "properties": {"requests": {"type": "array", "items": {"$ref": "#/components/schemas/PrivacyRequest"}}, "kinds": {"type": "object", "additionalProperties": {"type": "string"}}, "methods": {"type": "object", "additionalProperties": {"type": "string"}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "create_privacy_request", "x-handler": "create_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Log a visitor's request to see, delete, correct or stop using their data", "description": "`identifiers` are what the visitor gave: email, phone, the browser ID shown by `msa('privacy')` on your\nsite, or another tool's ID. They are kept only as hashes or IDs. IP addresses are refused (many people share\none). The due date is 30 days after `received_at` (default now). `send_link_to` (one of the identifiers, an\nemail) emails the visitor a one-time link that confirms the request is theirs.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["kind", "identifiers"], "properties": {"kind": {"type": "string", "enum": ["access", "delete", "correct", "stop"]}, "identifiers": {"type": "array", "items": {"type": "string"}, "maxItems": 10}, "received_at": {"type": "string", "example": "2026-11-11"}, "by_agent": {"type": "boolean", "description": "Made by an authorized agent for the visitor"}, "note": {"type": "string", "maxLength": 500}, "send_link_to": {"type": "string", "format": "email"}}}}}}, "responses": {"201": {"description": "Logged", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "get": {"operationId": "get_privacy_request", "x-handler": "get_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Get one request with its matches and a draft answer for the visitor", "responses": {"200": {"description": "The request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/verify-link": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "post": {"operationId": "send_privacy_verify_link", "x-handler": "send_privacy_link", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Email the visitor a one-time link that confirms the request is theirs", "description": "The email must be one the request was logged with. The link works for 7 days, once, and unlocks only data tied to\nthat email.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"email": {"type": "string", "format": "email"}}}}}}, "responses": {"200": {"description": "The request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/attest": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "post": {"operationId": "attest_privacy_request", "x-handler": "attest_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Record how you checked who is asking, without the email link", "description": "`browser_id`: the visitor gave you the browser ID that `msa('privacy')` showed them on your site (only that\nbrowser's data is reachable). `attested`: you checked by your own means (a signed-in customer, say); say how.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"method": {"type": "string", "enum": ["browser_id", "attested"]}, "reason": {"type": "string", "minLength": 10}}}}}}, "responses": {"200": {"description": "The request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/search": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "post": {"operationId": "search_privacy_request", "x-handler": "search_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Search every service for the verified visitor (runs in the background)", "description": "Answers at once; poll the request until `status` is `found`. Matches are `exact` or `guessed` (another browser on\nthe same device, a fingerprint match, a shared home connection).\n", "responses": {"202": {"description": "Search started", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/selection": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "put": {"operationId": "select_privacy_matches", "x-handler": "select_privacy_matches", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Choose which matches belong to the visitor", "description": "A copy of the data (`access`) may include exact matches only.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"keys": {"type": "array", "items": {"type": "string"}}}}}}}, "responses": {"200": {"description": "The request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/act": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "post": {"operationId": "act_on_privacy_request", "x-handler": "act_on_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Go ahead and make the copy, or delete the chosen data (runs in the background)", "description": "`access` makes an export kept 7 days. The other kinds delete the chosen data in every service and both Regions,\ncheck that nothing is left, then put the visitor's email and phone hashes on the client's do-not-match list.\n", "responses": {"202": {"description": "Started", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/export": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "get": {"operationId": "get_privacy_request_export", "x-handler": "privacy_request_export", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Download the copy of the visitor's data (JSON, kept 7 days)", "responses": {"200": {"description": "The export", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/extend": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "post": {"operationId": "extend_privacy_request", "x-handler": "extend_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Move the due date once, with a reason the visitor is told", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"days": {"type": "integer", "minimum": 1, "maximum": 45}, "reason": {"type": "string", "minLength": 10}}}}}}, "responses": {"200": {"description": "The request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/refuse": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "post": {"operationId": "refuse_privacy_request", "x-handler": "refuse_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Refuse the request, with a reason the visitor is told (they may appeal)", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"reason": {"type": "string", "minLength": 10}}}}}}, "responses": {"200": {"description": "The request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/close": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "post": {"operationId": "close_privacy_request", "x-handler": "close_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Close the request once the visitor has been answered", "description": "Only hashes and counts are kept, for 24 months; the matches and any export are deleted.\n", "responses": {"200": {"description": "The request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/privacy-requests/{request}/retry": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/privacyRequest"}], "post": {"operationId": "retry_privacy_request", "x-handler": "retry_privacy_request", "x-roles": ["admin", "tenant_admin"], "tags": ["Privacy requests"], "summary": "Carry on with a search, export or deletion that stopped", "responses": {"202": {"description": "Started again", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PrivacyRequest"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/sites": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "create_tracking_site", "x-handler": "create_site", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Add a website to track", "description": "Needs the Attribution service on for the client. Returns the site with the snippet to paste\ninto the website's <head>. One site per domain. The script works on that exact domain and\nthe subdomains listed in `subdomains`, and does nothing anywhere else.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingSiteInput"}}}}, "responses": {"201": {"description": "Created and published", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingSite"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/sites/{site_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "site_id", "in": "path", "required": true, "schema": {"type": "string", "example": "MS-7KQ2M9XP"}}], "get": {"operationId": "get_tracking_site", "x-handler": "get_site", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "A tracked website, with its snippet", "responses": {"200": {"description": "The site", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingSite"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "patch": {"operationId": "update_tracking_site", "x-handler": "update_site", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Change a tracked website's settings", "description": "Send only the fields to change. The site's script is published again (live within 5 minutes).", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingSiteInput"}}}}, "responses": {"200": {"description": "Saved and published", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingSite"}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_tracking_site", "x-handler": "delete_site", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Stop tracking a website", "description": "Removes the site and its script. Pages that still have the snippet load nothing.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/attribution/dashboard": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_tracking_dashboard", "x-handler": "attribution_dashboard", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Visits, visitors, sources and conversions for one site or all", "description": "Totals from the hourly job (an hour shows up about 15 minutes after it ends). Up to 3 days\nare shown by hour, longer ranges by day. Unique counts are estimates (about 3%).\n", "parameters": [{"name": "site", "in": "query", "schema": {"type": "string", "default": "all", "description": "A site ID", "or all": null}}, {"name": "range", "in": "query", "description": "The date range in UTC days: last_24_hours (by hour, up to the current hour) or any shared preset (today, yesterday, this_week, last_week, last_7_days ... last_12_months) or custom with start and end. Up to 400 days. Without range, the older days (1 to 400, default 7, today included) or from/to are used.", "schema": {"type": "string"}}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}, {"name": "days", "in": "query", "deprecated": true, "schema": {"type": "integer", "minimum": 1, "maximum": 400, "default": 7}}, {"name": "from", "in": "query", "deprecated": true, "schema": {"type": "string", "format": "date"}}, {"name": "to", "in": "query", "deprecated": true, "schema": {"type": "string", "format": "date"}}, {"name": "view", "in": "query", "description": "mapped (default) applies the client's tag rules to source, medium, campaign and channel; original shows the tags as they arrived. Both answers also carry `original`.", "schema": {"type": "string", "enum": ["mapped", "original"], "default": "mapped"}}], "responses": {"200": {"description": "Totals, breakdowns and a time series", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TrackingReport"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/realtime": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_attribution_realtime", "x-handler": "attribution_realtime", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "See who is on a website right now", "description": "Like GA4's Realtime, from the Attribution tag: browsers active in the last `minutes` minutes (default 30)\nand the last 5, new vs returning, sessions and conversions per minute (`per_minute`, oldest first, every\nminute present), top channels, sources, mediums, source / mediums, campaigns, landing pages, paid click IDs\n(gclid, fbclid ...), countries and devices (`top`, each `{value, visitors}`), conversions (form submits,\nand orders or leads the site reports with them), `locations` for a live map (`{lat, lon, label, visitors,\nconversions}`: the visitor's time zone city or country, never a precise place) and `recent`, the latest\nstate of up to 20 visitors. Both regions are read and added up; `partial` is true when one didn't answer,\n`sampled` when a very busy site had more rows than one answer reads. Nothing in the answer identifies a\nperson. The same answer as the API/MCP Gateway tool `attribution_realtime`.\n", "parameters": [{"name": "site", "in": "query", "schema": {"type": "string", "description": "A site ID (MS-XXXXXXXX); default the client's first website"}}, {"name": "minutes", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 60, "default": 30}}, {"name": "view", "in": "query", "description": "mapped (default) applies the client's tag rules to source, medium, campaign and channel; original shows them as they arrived.", "schema": {"type": "string", "enum": ["mapped", "original"], "default": "mapped"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/conversions": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_offline_conversions", "x-handler": "get_conversions", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Offline conversion settings, the choices for a mapping, and the latest sends", "description": "Offline conversions send a client's orders, leads and calls back to the ad platform that brought the\nvisitor (Google Ads, Meta, Microsoft Ads), once each, when the record is linked to a visitor. Nothing is\nsent until a mapping exists. Visitors who said no to ads on the site's consent banner are never sent,\nand only hashed emails and phones and click IDs leave Metric Sensei. `connections` lists the client's\nad connections per platform; `log` the latest 50 sends.\n", "responses": {"200": {"description": "Settings and choices", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "update_offline_conversions", "x-handler": "put_conversions", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Save offline conversion settings and mappings", "description": "Settings left out keep their value. Each mapping sends one event type (`shopify_order`,\n`woocommerce_order`, `wordpress_form`, `blazeo_lead`, `callrail_call`, `callrail_form`,\n`hubspot_contact`, `netsuite_lead`, `netsuite_sale`, `ringcentral_call`, `salesforce_lead`,\n`salesforce_won`) to one conversion action: `platform` google_ads (account_id = customer ID, action = conversion action ID),\nmeta (account_id = Pixel/dataset ID, action = event name such as Purchase or Lead) or microsoft_ads\n(account_id = account ID, action = the offline conversion goal's name). `value` is \"record\" (the\norder total), a fixed number, or empty. CallRail calls count only from `min_call_seconds` (default\n60); RingCentral calls when they were inbound and answered.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConversionSettings"}}}}, "responses": {"200": {"description": "Saved", "content": {"application/json": {"schema": {"type": "object", "properties": {"settings": {"$ref": "#/components/schemas/ConversionSettings"}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/conversions/log": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_offline_conversion_sends", "x-handler": "conversions_log", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "The latest offline conversion sends, newest first", "parameters": [{"name": "status", "in": "query", "schema": {"type": "string", "enum": ["sent", "skipped", "failed", "sending"]}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}}], "responses": {"200": {"description": "Sends, each with its status and the reason when skipped or failed", "content": {"application/json": {"schema": {"type": "object", "properties": {"log": {"type": "array", "items": {"type": "object", "additionalProperties": true}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/conversions/send-again": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "resend_offline_conversion", "x-handler": "conversions_send_again", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Send a failed offline conversion again", "description": "Pass `received_at`, `mapping`, `source` and `event_id` from a failed log row. Sent ones are never sent twice.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["received_at", "mapping", "source", "event_id"], "properties": {"received_at": {"type": "string"}, "mapping": {"type": "string"}, "source": {"type": "string"}, "event_id": {"type": "string"}}}}}}, "responses": {"200": {"description": "What happened per mapping", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/conversions/backfill": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "backfill_offline_conversions", "x-handler": "conversions_backfill", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Send the last N days of stored events through the mappings", "description": "Runs in the background (202). Events already sent are skipped, so running it twice is safe. Up to\n90 days, the longest Google Ads takes conversions after a click.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"days": {"type": "integer", "minimum": 1, "maximum": 90, "default": 30}}}}}}, "responses": {"202": {"description": "Queued", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "200": {"description": "Done right away (environments without a job queue)", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/datamanager/uploads": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_datamanager_uploads", "x-handler": "datamanager_uploads", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "The client's latest Google Data Manager audience uploads, newest first", "description": "Counts and IDs only (people's details are never stored). Each has the request_id Google gave it.", "parameters": [{"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}}], "responses": {"200": {"description": "Uploads", "content": {"application/json": {"schema": {"type": "object", "properties": {"uploads": {"type": "array", "items": {"type": "object", "additionalProperties": true}}}}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/datamanager/status": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_datamanager_upload_status", "x-handler": "datamanager_status", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "How Google processed one Data Manager upload", "description": "Per destination: PROCESSING, SUCCESS, PARTIAL_SUCCESS or FAILED, with record counts, errors by reason and\nthe match-rate range. Uploads usually finish within a few hours. For an offline conversion in the\nConversions send log, pass `service=ads` and the mapping's Google Ads connection.\n", "parameters": [{"name": "request_id", "in": "query", "required": true, "schema": {"type": "string"}}, {"name": "connection", "in": "query", "required": true, "schema": {"type": "string"}}, {"name": "service", "in": "query", "schema": {"type": "string", "enum": ["datamanager", "ads"], "default": "datamanager"}}], "responses": {"200": {"description": "Status per destination", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/datamanager/{connection}/audiences": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "connection", "in": "path", "required": true, "description": "The client's Google Data Manager connection", "schema": {"type": "string"}}], "get": {"operationId": "list_google_ads_audiences", "x-handler": "datamanager_audiences", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "A Google Ads account's audiences, with size and Customer Match match rate", "parameters": [{"name": "customer_id", "in": "query", "description": "Google Ads account ID (default: the connection's)", "schema": {"type": "string"}}, {"name": "only_customer_match", "in": "query", "schema": {"type": "boolean"}}], "responses": {"200": {"description": "Audiences", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "create_google_ads_audience", "x-handler": "datamanager_create_audience", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Create a Customer Match audience (emails and phone numbers) in Google Ads", "description": "Needs write access on the connection. The audience's description ends with \"[Metric Sensei]\", so deleting the client empties it.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["name"], "properties": {"customer_id": {"type": "string"}, "name": {"type": "string", "maxLength": 200}, "description": {"type": "string"}, "membership_days": {"type": "integer", "minimum": 1, "maximum": 540, "default": 540}}}}}}, "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/datamanager/{connection}/audiences/{audience}/members": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "connection", "in": "path", "required": true, "description": "The client's Google Data Manager connection", "schema": {"type": "string"}}, {"name": "audience", "in": "path", "required": true, "description": "Audience ID (digits)", "schema": {"type": "string"}}], "post": {"operationId": "upload_google_ads_audience_members", "x-handler": "datamanager_members", "x-roles": ["admin", "tenant_admin"], "tags": ["Data sources"], "summary": "Add people to (or remove them from) a Customer Match audience", "description": "Up to 10,000 people, as `people` (emails, or objects with email/phone, emails/phones) or `csv` text with\nan email and/or phone column (or one email or phone per line). Emails and phones are normalized and\nSHA-256 hashed before they leave Metric Sensei and are never stored. Adding needs the connection's\ncustomer_match_terms set to \"accepted\"; both need write access on the connection. `validate_only` has\nGoogle check the upload without saving it.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"action": {"type": "string", "enum": ["add", "remove"], "default": "add"}, "people": {"type": "array", "maxItems": 10000, "items": {}}, "csv": {"type": "string"}, "customer_id": {"type": "string"}, "default_country_code": {"type": "string", "description": "Calling code for phones without one (default 1)"}, "ad_user_data": {"type": "string", "enum": ["granted", "denied"]}, "ad_personalization": {"type": "string", "enum": ["granted", "denied"]}, "validate_only": {"type": "boolean"}}}}}}, "responses": {"200": {"description": "Sent; check with the status operation using request_id", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/tag-rules": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_tag_rules", "x-handler": "get_tag_rules", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "A client's tag rules, versions, tag values and what the rules change", "description": "Tag rules clean up campaign tags (source, medium, campaign, term, content) and group visits into channels.\nThey apply when reports are read: the original tags are never changed, so new rules apply to all\nhistory at once. Also returns the distinct tag values of the last 90 days with counts, what the\nrules change (`changes`), values no rule covers yet (`unmapped`, tag health) and the clean values a\nlink builder offers (`approved`).\n", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "update_tag_rules", "x-handler": "put_tag_rules", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Save tag rules as a new version", "description": "Body: `lowercase`, `values` (field, match exact|contains|starts|regex, value with | between\nalternatives, to), `channels` (name, when: conditions on mapped values, all must hold) and\n`default_channels` (GA4-style groups for anything unmatched). Saved versions are kept.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagRules"}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/tag-rules/restore": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "restore_tag_rules", "x-handler": "restore_tag_rules", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Bring back an older version of the tag rules", "description": "Saves the chosen version again as the newest one.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["version"], "properties": {"version": {"type": "integer"}}}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/tag-rules/suggest": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "suggest_tag_rules", "x-handler": "suggest_tag_rules", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "AI-suggested tag rules (not saved)", "description": "Claude reviews the client's distinct tag values (last 90 days; only values and counts are sent) and\nsuggests merges and channel groups, with what they would change. Nothing is saved until the rules\nare sent to PUT tag-rules. Counts as AI usage.\n", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/links/build": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "build_utm_link", "x-handler": "build_utm_link", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Build a campaign (UTM) link with clean tags", "description": "The tags are cleaned by the client's tag rules first, so new links arrive standardized. `save: true`\nkeeps the link (with `name`) in the client's saved links.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["url", "source", "medium"], "properties": {"url": {"type": "string"}, "source": {"type": "string"}, "medium": {"type": "string"}, "campaign": {"type": "string"}, "term": {"type": "string"}, "content": {"type": "string"}, "save": {"type": "boolean"}, "name": {"type": "string"}}}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/links": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_utm_links", "x-handler": "list_utm_links", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Saved campaign links", "description": "Newest first.\n", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/attribution/links/{link_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "link_id", "in": "path", "required": true, "schema": {"type": "string"}}], "delete": {"operationId": "delete_utm_link", "x-handler": "delete_utm_link", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Forget a saved campaign link", "description": "The link itself keeps working.\n", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/tracked-sites/{site_id}/proxy": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "site_id", "in": "path", "required": true, "schema": {"type": "string", "example": "MS-ABCD1234"}}], "get": {"operationId": "get_site_proxy", "x-handler": "get_site_proxy", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Whether a website has first-party mode on (never the key)", "description": "In first-party mode the website serves our tag and forwards its calls from its own address\n(`<path>/t.js`, `<path>/m/<file>`, `<path>/i`, `<path>/a`), so browsers keep visitor IDs as\nfirst-party data and ad blockers can't tell the tag from the site.\n", "responses": {"200": {"description": "The proxy's state", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "enable_site_proxy", "x-handler": "put_site_proxy", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Turn on first-party mode, or make a new proxy key", "description": "Returns the proxy key once, with ready-to-install Cloudflare Worker and Nginx code and the script\ntag to put in the page. Only the key's hash is kept; a new key stops the old one. WordPress sites\nwith the Metric Sensei plugin turn this on by themselves.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"path": {"type": "string", "default": "/_s", "pattern": "^/[A-Za-z0-9_-]{1,30}$"}}}}}}, "responses": {"200": {"description": "On, with the key and the code to install", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "disable_site_proxy", "x-handler": "delete_site_proxy", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Turn off first-party mode", "description": "The site's tag goes back to the CDN. Put the normal snippet back in the page first.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/attribution/lookup": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "lookup_visitor", "x-handler": "attribution_lookup", "x-roles": ["admin", "tenant_admin"], "tags": ["Tracking"], "summary": "Everything we know about one visitor, person or household", "description": "Type what you have in `q`: one of our IDs (browser, device, person or household, 32 hex\ncharacters), an email address or phone number (normalized and SHA-256 hashed the way the\nattribution script does; the plain text is never stored), a SHA-256 hash, an IP address, or\nanother platform's ID as it is (`GA1.1.123.456`, a GA client ID like `1234567890.1700000000`, a\nHubSpot or Segment ID ...). Other platforms' IDs are tried as every ID type we collect, and text\nthat could also be one of our IDs is searched both ways; `matched_ids` says which types matched.\nWrite `_ga=GA1.1.123.456` to search one cookie only. Only this client's visitors are searched.\n\nThe answer lists the people, browsers and households found, other platforms' IDs, hashed contact\ndetails and, where the website keeps them (`keep_contact`), the readable contact details,\ndecrypted only for this answer. The visit history (sessions, form submits, identify calls and the\ncampaign behind each visit) covers up to the last 95 days.\n", "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string", "maxLength": 300, "example": "ann@example.com"}}, {"name": "days", "in": "query", "description": "How far back the visit history goes (1 to 95 days).", "schema": {"type": "integer", "minimum": 1, "maximum": 95, "default": 95}}], "responses": {"200": {"description": "What was found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VisitorLookup"}}}}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/nms/{op}": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "nms_query", "x-handler": "nms_read", "x-roles": ["everyone"], "tags": ["Network Monitoring"], "summary": "Read Network Monitoring data", "description": "Runs one read-only Network Monitoring operation, with its arguments as query parameters (all optional\nunless noted): `status` (location_id, group_id), `list_groups` (device groups, which can hold devices from\nany location, with members per location), `list_locations`, `list_collectors`, `list_devices` (location_id,\ngroup_id, status), `get_device` (node_id, required), `list_found` (discovered devices waiting), `list_monitors`,\n`shared_status` (the shared API endpoints of the client's data sources, checked once for all clients),\n`list_alarms` (location_id, group_id, min_severity, all, limit), `list_events` (day yyyy-mm-dd, limit), `list_outages`\n(since, until unix seconds; target or group_id), `metric_series` (target and metric required; start, end),\n`list_maintenance`, `get_settings`, `list_snmp_profiles` and `list_rules` (admins; secrets are never shown),\n`get_contact` (your own alert email and mobile number), `topology` (network overview: the network gear,\nits links from LLDP/CDP and a connection list; location_id), `site_map` (every device on its switch port;\nlocation_id), `connected_devices` (every MAC seen with IP, VLAN, switch and port; unmonitored, location_id),\n`list_unifi`, `config_platforms`, `config_versions` / `config_text` / `config_diff` (admins; node_id, at,\na, b), `flows_top` (top talkers, apps, sources, destinations from NetFlow/IPFIX/sFlow; target, start,\nend, limit), `availability_report` (month yyyy-mm; location_id, group_id), `alarm_context` (alarm_id;\neverything around an alarm, for root cause), `list_event_rules`, `list_thresholds`, `list_credentials`\n(admins; secrets never shown).\n", "parameters": [{"name": "op", "in": "path", "required": true, "schema": {"type": "string", "enum": ["status", "topology", "site_map", "connected_devices", "list_unifi", "config_platforms", "config_versions", "config_text", "config_diff", "flows_top", "availability_report", "alarm_context", "list_event_rules", "list_thresholds", "list_credentials", "list_groups", "list_locations", "list_collectors", "list_devices", "get_device", "list_found", "list_monitors", "shared_status", "list_alarms", "list_events", "list_outages", "metric_series", "list_maintenance", "get_settings", "list_snmp_profiles", "list_rules", "get_contact"]}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "nms_change", "x-handler": "nms_change", "x-roles": ["everyone"], "tags": ["Network Monitoring"], "summary": "Change Network Monitoring setup or alarms", "description": "Runs one Network Monitoring change; the body is the operation's arguments, the same JSON the Network page\nsends. Admins: `save_location` (location_id to change one; name, networks [CIDRs], discovery {ranges,\nexclude, every_hours, auto_add rules, snmp_profiles}), `delete_location`, `collector_setup_code`\n(location_id; a one-time code for a new collector), `remove_collector` (collector_id), `save_device`\n(node_id to change one; ip, label, location_id, snmp_profile, checks, tags; probe: auto - public addresses\nfrom our cloud probes, private ones from the location's collector - or collector or cloud), `delete_device`, `save_monitor`\n/ `delete_monitor` (cloud checks of public URLs and hosts), `save_group` / `delete_group` (group_id to\nchange one; name, description, devices [node_ids], match {vendor, type, tags, location_ids, network,\nname_contains} to add devices automatically), `save_snmp_profile` / `delete_snmp_profile`,\n`save_rule` / `delete_rule` (notification rules: match, delay_minutes, steps of email, sms, slack, teams,\npagerduty or webhook channels; match.group_ids aims a rule at device groups), `save_event_rule` /\n`delete_event_rule` (trap and syslog rules: match {kind, trap_oid, text_contains, program,\nmax_syslog_severity, location_ids, group_ids}, action alarm/event/ignore, severity, clears {...}),\n`save_threshold` / `delete_threshold` (metric, op > or <, value, rearm, samples, severity, scope),\n`save_credential` / `delete_credential` (kind ssh, winrm or unifi; username, password, private_key, api_key,\nurl), `save_unifi` / `delete_unifi` (mode local or cloud, credential, location_id, sites), `save_maintenance` /\n`delete_maintenance` (scope.group_ids covers device groups), `save_settings` (auto_websites,\nauto_sources, retention_days). Anyone in the client: `acknowledge_alarm` (alarm_id, ack), `clear_alarm`\n(alarm_id), `save_contact` (email, mobile in +country format, email_on, sms_on).\n", "parameters": [{"name": "op", "in": "path", "required": true, "schema": {"type": "string", "enum": ["acknowledge_alarm", "clear_alarm", "save_location", "delete_location", "collector_setup_code", "remove_collector", "save_device", "delete_device", "save_monitor", "delete_monitor", "save_group", "delete_group", "save_event_rule", "delete_event_rule", "save_threshold", "delete_threshold", "save_credential", "delete_credential", "save_unifi", "delete_unifi", "save_snmp_profile", "delete_snmp_profile", "save_rule", "delete_rule", "save_maintenance", "delete_maintenance", "save_settings", "save_contact"]}}], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboards": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_dashboards", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards", "x-list": {"key": "dashboards"}, "tags": ["Dashboards"], "summary": "List a client's dashboards", "description": "The dashboards you can see: your own, the ones shared with you and, for admins, all of them. Each says whether it is yours and how it was made.\n", "parameters": [{"name": "made_by_ai", "in": "query", "description": "Send 1 for only the dashboards the AI assistant made.", "schema": {"type": "string"}}, {"name": "chat_id", "in": "query", "description": "Only the dashboards made in this AI chat.", "schema": {"type": "string"}}, {"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "create_dashboard", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards", "tags": ["Dashboards"], "summary": "Create a dashboard", "description": "Makes a dashboard from a spec: its name, date range, refresh interval and tiles. It is private to you until you share it. Use List dashboard data tools for what a tile can show, and Preview a dashboard tile to try one first.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"spec": {"type": "object", "description": "The dashboard: name, description, date_range, refresh_minutes, timezone and tiles."}}}}}}, "responses": {"201": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboards/{dashboard_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/dashboard_id"}], "get": {"operationId": "get_dashboard", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}", "tags": ["Dashboards"], "summary": "Get a dashboard", "description": "One dashboard: its spec, sharing, daily email settings and what you may do with it.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "save_dashboard", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}", "tags": ["Dashboards"], "summary": "Save a dashboard", "description": "Replaces the dashboard's spec. Send the `version` you read, so two people editing at once don't overwrite each other: a stale version is refused.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"spec": {"type": "object", "description": "The whole dashboard spec."}, "version": {"type": "integer", "description": "The version you read. Leave out to save regardless."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_dashboard", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}", "tags": ["Dashboards"], "summary": "Delete a dashboard", "description": "Deletes the dashboard, its public link and its daily email. Only its owner or an admin of the client can.\n", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/dashboards/{dashboard_id}/data": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/dashboard_id"}], "get": {"operationId": "get_dashboard_data", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}/data", "tags": ["Dashboards"], "summary": "Get a dashboard's data", "description": "The numbers for every tile, read with your own access to the client's connections. Kept for the dashboard's refresh interval; `refresh=1` reads fresh (and counts as usage). The date parameters show other dates for this answer only (the dashboard keeps its own); compare=none leaves comparisons out.\n", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}, {"name": "refresh", "in": "query", "description": "Send 1 to read fresh data now.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboards/{dashboard_id}/sharing": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/dashboard_id"}], "put": {"operationId": "share_dashboard", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}/sharing", "tags": ["Dashboards"], "summary": "Share a dashboard", "description": "Who in the client can see it: `private` (only you), `account` (everyone in the client) or `people` (the usernames in `people`).\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"mode": {"type": "string", "description": "private, account or people."}, "people": {"type": "array", "description": "Usernames, with mode people."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboards/{dashboard_id}/public-link": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/dashboard_id"}], "post": {"operationId": "create_dashboard_public_link", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}/public-link", "tags": ["Dashboards"], "summary": "Make a public link to a dashboard", "description": "Admins only. Returns a link anyone can open without signing in, for `days` days. Making a new one replaces the old one.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"days": {"type": "integer", "description": "How long the link works."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "remove_dashboard_public_link", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}/public-link", "tags": ["Dashboards"], "summary": "Turn a dashboard's public link off", "description": "The link stops working at once.\n", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/dashboards/{dashboard_id}/email": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/dashboard_id"}], "put": {"operationId": "set_dashboard_email", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}/email", "tags": ["Dashboards"], "summary": "Set a dashboard's daily email", "description": "Who gets the dashboard by email each day, and when. Send the settings object the dashboard shows under `email`; an empty list of people turns it off.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"enabled": {"type": "boolean", "description": "Whether the daily email is on."}, "people": {"type": "array", "description": "Usernames who get it."}, "hour": {"type": "integer", "description": "Hour of the day to send, in the time zone of the dashboard."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboards/{dashboard_id}/email/test": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/dashboard_id"}], "post": {"operationId": "send_dashboard_email_test", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}/email/test", "tags": ["Dashboards"], "summary": "Send a dashboard's email to yourself now", "description": "Sends the dashboard's email to your own address, so you can see it before turning the daily email on.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboards/{dashboard_id}/export": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/dashboard_id"}], "post": {"operationId": "export_dashboard", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "dashboards/{dashboard_id}/export", "tags": ["Dashboards"], "summary": "Export a dashboard", "description": "Makes a PDF, Excel or PNG copy and returns a download link (`url`) that works for five minutes, with a file `name`.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"format": {"type": "string", "description": "pdf (default), xlsx or png."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboard-sources": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_dashboard_sources", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "sources", "tags": ["Dashboards"], "summary": "List the data tools dashboard tiles can use", "description": "The client's connected data, as the tools a tile can read from, with the arguments each takes.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboard-preview": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "preview_dashboard_tile", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "preview", "tags": ["Dashboards"], "summary": "Preview a dashboard tile", "description": "Reads one tile's data without saving anything, to try a tile before adding it.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"tile": {"type": "object", "description": "The tile, as it would go in a dashboard spec."}, "date_range": {"type": "string", "description": "For example last_28_days."}, "timezone": {"type": "string", "description": "An IANA time zone name."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboard-people": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_dashboard_people", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "people", "tags": ["Dashboards"], "summary": "List the people a dashboard can be shared with", "description": "The client's people, for sharing and the daily email.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/dashboard-access": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_dashboard_access", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "dashboards", "x-service-path": "me", "tags": ["Dashboards"], "summary": "See what you may do with a client's dashboards", "description": "Your role in the client as Dashboards sees it, and whether you may edit, share publicly and email.\n", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/access": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_analytics_access", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "me", "tags": ["Analytics"], "summary": "See what you may do in a client's Analytics", "description": "Your role in the client as Analytics sees it.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/preferences": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_analytics_preferences", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "me/preferences", "tags": ["Analytics"], "summary": "Get your Analytics preferences", "description": "Which numbers Analytics reports open on for you in this client (`data`): auto (blended on websites with GA4 linked, our tag's numbers elsewhere), ours, ga4 or blended. A saved ga4 or blended choice shows our tag's numbers on websites without GA4.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "set_analytics_preferences", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "me/preferences", "tags": ["Analytics"], "summary": "Set your Analytics preferences", "description": "Choose which numbers Analytics reports open on for you in this client, like the Numbers switch on the Analytics screen. It applies when a report call doesn't send `data`, here and in the AI assistant. auto goes back to the default.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["data"], "properties": {"data": {"type": "string", "enum": ["auto", "ours", "ga4", "blended"], "description": "auto (blended where GA4 is linked, else ours), ours, ga4 or blended."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_analytics_sites", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites", "x-list": {"key": "sites"}, "tags": ["Analytics"], "summary": "List a client's websites with Analytics on", "description": "The websites Analytics measures for this client, with their time zone and linked data sources. The other Analytics operations take a website's `site_id` from here.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}}, "/clients/{client}/analytics/sites/{site_id}/overview": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_overview", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/overview", "tags": ["Analytics"], "summary": "Analytics overview for a website", "description": "Headline numbers for the date range (visitors, visits, page views, conversions, revenue), the change against the period before, and a day-by-day chart.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/analytics_data"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/breakdown/{dimension}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_dimension"}], "get": {"operationId": "get_analytics_breakdown", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/breakdown/{dimension}", "tags": ["Analytics"], "summary": "Break a website's traffic down by one dimension", "description": "Visitors and conversions grouped by one dimension: channel, source, medium, source_medium, campaign, content, term, page, entry_page, referrer, click_id, country, region, city, device, browser, os, language or hostname.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}, {"$ref": "#/components/parameters/analytics_data"}, {"name": "raw", "in": "query", "description": "Send 1 for the source and medium as they arrived, before the client's tag rules.", "schema": {"type": "string"}}, {"name": "limit", "in": "query", "description": "How many rows (default 50).", "schema": {"type": "integer"}}, {"name": "q", "in": "query", "description": "Only rows containing this text.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/events": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_events", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/events", "tags": ["Analytics"], "summary": "List a website's events", "description": "The events the website sent in the date range: `rows` (each event's count, visits and conversion rate), `properties` (text parameter values per event), `numbers` (number parameters per event: count, sum and average), `items` (GA4-style items per event: events, quantity and revenue) and `currency`. How websites send events: the tracking JavaScript API guide at /docs/tracking.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/goals": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_goals", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/goals", "tags": ["Analytics"], "summary": "Get a website's goal conversions", "description": "Conversions for each goal in the date range, by day.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}, {"$ref": "#/components/parameters/analytics_data"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/live": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_live", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/live", "tags": ["Analytics"], "summary": "See a website's visitors right now", "description": "Like GA4's Realtime: visitors in the last `minutes` minutes (default 30; `last_30_minutes` and\n`last_5_minutes` stay for older callers), visitors, page views and conversions per minute, top pages,\nchannels, sources, campaigns, countries, devices, events and goals, `locations` for a live map (`{lat, lon,\nlabel, visitors, conversions}`, city level, rounded to 0.1 degree) and recent activity. Source, medium,\ncampaign and channel use the client's tag rules unless `raw=true`. Both regions are read and added up.\nNothing in the answer identifies a person. The same answer as the API/MCP Gateway tool `analytics_live`.\n", "parameters": [{"name": "minutes", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 60, "default": 30}}, {"name": "raw", "in": "query", "schema": {"type": "boolean", "default": false}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/revenue": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_revenue", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/revenue", "tags": ["Analytics"], "summary": "Get a website's revenue", "description": "Revenue in the date range, grouped by channel, source and medium, campaign, entry page or event.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}, {"name": "by", "in": "query", "description": "channel (default), source_medium, campaign, entry_page or event.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/vitals": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_vitals", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/vitals", "tags": ["Analytics"], "summary": "Get a website's Core Web Vitals", "description": "Loading, responsiveness and layout stability as real visitors experienced them (75th percentile), by page and device.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/cohorts": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_cohorts", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/cohorts", "tags": ["Analytics"], "summary": "Get a website's visitor cohorts", "description": "How many visitors who first came in each week or month came back in the weeks or months after.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"name": "unit", "in": "query", "description": "week (default) or month.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/paths": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_paths", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/paths", "tags": ["Analytics"], "summary": "See where visitors go before or after a page", "description": "The most common next or previous pages for one page.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"name": "page", "in": "query", "description": "The page, for example /pricing.", "schema": {"type": "string"}}, {"name": "direction", "in": "query", "description": "next (default) or previous.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/heatmap": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_heatmap", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/heatmap", "tags": ["Analytics"], "summary": "Get a page's click heatmap", "description": "Where visitors clicked on one page, for one kind of device.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"name": "page", "in": "query", "description": "The page, for example /.", "schema": {"type": "string"}}, {"name": "device", "in": "query", "description": "Desktop, Mobile or Tablet.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/scroll": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_scroll", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/scroll", "tags": ["Analytics"], "summary": "Get a page's scroll depth", "description": "How far down one page visitors scrolled.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"name": "page", "in": "query", "description": "The page, for example /.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/heatmap-link": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "post": {"operationId": "create_analytics_heatmap_link", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/heatmap-link", "tags": ["Analytics"], "summary": "Make a link that shows a heatmap over the live page", "description": "Returns a link that opens the real page with its click heatmap drawn on top. The link works for one hour.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"page": {"type": "string", "description": "The page, for example /."}, "range": {"type": "string", "description": "The date range, for example last_28_days."}, "device": {"type": "string", "description": "Desktop, Mobile or Tablet."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/segment": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "post": {"operationId": "query_analytics_segment", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/segment", "x-read-only": true, "tags": ["Analytics"], "summary": "Ask about a segment of a website's visitors", "description": "Numbers for only the visitors matching a set of filters (each with a field, an operator and a value), or a saved segment by name, optionally broken down by one dimension. It only reads; it is a POST because the question can be long.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"filters": {"type": "array", "description": "Filters, each {field, op, value}."}, "segment": {"type": "string", "description": "Or the name of a saved segment."}, "breakdown": {"type": "string", "description": "A dimension to group by."}, "range": {"type": "string", "description": "The date range."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/exports": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "post": {"operationId": "create_analytics_export", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/exports", "tags": ["Analytics"], "summary": "Export a website's raw events", "description": "Starts an export of the date range's events and returns an `export_id`. Read it with Get an Analytics export until it has a download link.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"range": {"type": "string", "description": "The date range."}, "start": {"type": "string", "description": "With range custom: yyyy-mm-dd."}, "end": {"type": "string", "description": "With range custom: yyyy-mm-dd."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/exports/{export_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_export_id"}], "get": {"operationId": "get_analytics_export", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/exports/{export_id}", "tags": ["Analytics"], "summary": "Get an Analytics export", "description": "The export's state and, once it is ready, a download link.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/alerts": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "list_analytics_alerts", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/alerts", "tags": ["Analytics"], "summary": "List a website's traffic alerts", "description": "Unusual drops and spikes Analytics noticed in the last 30 days.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/replays": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "list_analytics_replays", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/replays", "tags": ["Analytics"], "summary": "List a website's session recordings", "description": "Recorded visits (an add-on), newest first, optionally only those that included one page.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"name": "page", "in": "query", "description": "Only visits that included this page.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/replays/{day}/{replay_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_replay_day"}, {"$ref": "#/components/parameters/analytics_replay_id"}], "get": {"operationId": "get_analytics_replay", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/replays/{day}/{replay_id}", "tags": ["Analytics"], "summary": "Get a session recording", "description": "Signed links to one recording's parts, for the player. They work for a few minutes.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/experiments": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "list_analytics_experiments", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/experiments", "tags": ["Analytics"], "summary": "List a website's A/B tests and feature flags", "description": "The website's experiments and flags (an add-on) and their state.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "save_analytics_experiment", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/experiments", "tags": ["Analytics"], "summary": "Create or change an A/B test or feature flag", "description": "Creates or changes an A/B test as a draft, or stops one. Client and agency admins only. Starting a test, and changing one that is running, happens on the admin page, which republishes the website's tag.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"name": {"type": "string", "description": "The test's name."}, "key": {"type": "string", "description": "Its key. Defaults to one made from the name."}, "variants": {"type": "array", "description": "The variants and their weights."}, "status": {"type": "string", "description": "draft (default) or stopped."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/experiments/{experiment_key}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_experiment_key"}], "get": {"operationId": "get_analytics_experiment", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/experiments/{experiment_key}", "tags": ["Analytics"], "summary": "Get an A/B test's results", "description": "Visitors, conversions and the chance of being best, per variant.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/tracked-sites/{site_id}/smart-events/{rule_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "site_id", "in": "path", "required": true, "schema": {"type": "string", "example": "MS-ABCD1234"}}, {"name": "rule_id", "in": "path", "required": true, "description": "The rule's ID (lower-case letters, digits, - and _), e.g. se-quote-request.", "schema": {"type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{0,59}$"}}], "put": {"operationId": "save_smart_event", "x-handler": "save_smart_event", "x-roles": ["admin", "tenant_admin"], "tags": ["Analytics"], "summary": "Create or change a smart event (tracked at once)", "description": "A smart event is a rule the website's Analytics tag matches with no code on the site; each match counts\nan event with the rule's name. The website's tag is published again, so the change is live within\nminutes. Rules can't watch form fields or anything a visitor types. A website has up to 30.\n`goal` true also makes the event a website goal (false removes that goal).\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["rule"], "properties": {"rule": {"type": "object", "required": ["name", "type"], "properties": {"name": {"type": "string", "description": "The event's name (60 characters at most)."}, "type": {"type": "string", "enum": ["click", "submit", "page", "visible"]}, "selector": {"type": "string", "description": "CSS selector of the element (click, submit, visible)."}, "link": {"type": "string", "enum": ["tel", "mailto", "sms", "whatsapp"], "description": "Clicks on this kind of link."}, "href": {"type": "string", "description": "Clicks on links whose address contains this."}, "text": {"type": "string", "description": "Clicks on a button or link with this label."}, "form": {"type": "string", "description": "Submits of the form with this id or name."}, "path": {"type": "string", "description": "Page rules: the path (a trailing * matches the rest)."}, "pages": {"type": "array", "items": {"type": "string"}, "description": "Only on these pages."}, "on": {"type": "boolean", "description": "false keeps the rule without tracking it."}}}, "goal": {"type": "boolean", "description": "Make it a website goal (true) or not (false)."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_smart_event", "x-handler": "delete_smart_event", "x-roles": ["admin", "tenant_admin"], "tags": ["Analytics"], "summary": "Delete a smart event", "description": "Stops tracking the event (its counts so far stay in the reports) and removes its goal.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/tracked-sites/{site_id}/smart-events/suggestions/{suggestion_id}/approve": {"parameters": [{"$ref": "#/components/parameters/client"}, {"name": "site_id", "in": "path", "required": true, "schema": {"type": "string", "example": "MS-ABCD1234"}}, {"$ref": "#/components/parameters/analytics_suggestion_id"}], "post": {"operationId": "approve_smart_event_suggestion", "x-handler": "approve_smart_suggestion", "x-roles": ["admin", "tenant_admin"], "tags": ["Analytics"], "summary": "Approve a suggested smart event (it is tracked from now on)", "description": "Turns a suggestion (from Find events, the event assistant or Website Optimizer) into a live smart event\nand publishes the website's tag. The name and rule may be changed first; `goal` overrides the\nsuggestion's advice on making it a website goal.\n", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}, "rule": {"type": "object", "additionalProperties": true}, "goal": {"type": "boolean"}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/smart-events": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "list_analytics_smart_events", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/smart-events", "tags": ["Analytics"], "summary": "List a website's smart events and suggestions", "description": "The smart events tracked now (count in the range, and a warning when one stopped matching), suggestions waiting for review, approved and dismissed ones, the latest Find events run and the client's event goals. Default range: last 30 days.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/smart-events/discover": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "post": {"operationId": "find_analytics_smart_events", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/smart-events/discover", "tags": ["Analytics"], "summary": "Find smart events on a website (AI)", "description": "Looks at the website's pages and what is already measured, and suggests key events for the client's goals. Runs in the background for up to a minute: list the smart events again to see the run finish. Client and agency admins only; counts as one Analytics AI run.", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"goals": {"type": "object", "properties": {"primary": {"type": "string", "enum": ["leads", "calls", "sales", "bookings", "signups", "content", "brand"]}, "notes": {"type": "string"}}}, "pages": {"type": "array", "items": {"type": "string"}, "description": "Extra pages to look at (paths)."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/smart-events/suggestions": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "post": {"operationId": "suggest_analytics_smart_event", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/smart-events/suggestions", "tags": ["Analytics"], "summary": "Suggest a smart event (waits for approval)", "description": "Drafts a smart event for someone to approve; it isn't tracked until then. Client and agency admins only.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["rule"], "properties": {"rule": {"type": "object", "additionalProperties": true, "description": "As in Create or change a smart event."}, "why": {"type": "string"}, "goal": {"type": "boolean"}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/smart-events/suggestions/{suggestion_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_suggestion_id"}], "get": {"operationId": "get_analytics_smart_event_suggestion", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/smart-events/suggestions/{suggestion_id}", "tags": ["Analytics"], "summary": "Get one suggested smart event", "description": "What it tracks, where it was found, why, its estimated count and its status.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "review_analytics_smart_event_suggestion", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/smart-events/suggestions/{suggestion_id}", "tags": ["Analytics"], "summary": "Dismiss a suggested smart event, or bring one back", "description": "action dismiss or restore. Approving is Approve a suggested smart event. Client and agency admins only.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["action"], "properties": {"action": {"type": "string", "enum": ["dismiss", "restore"]}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/smart-events/assistant": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "post": {"operationId": "ask_analytics_event_assistant", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/smart-events/assistant", "tags": ["Analytics"], "summary": "Ask the smart events assistant", "description": "One reply in a chat with the assistant, which asks about the client's goals and proposes smart events (added as suggestions for approval). Send the whole chat each time. Client and agency admins only; each reply counts as one Analytics AI run.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["messages"], "properties": {"messages": {"type": "array", "items": {"type": "object", "properties": {"role": {"type": "string", "enum": ["user", "assistant"]}, "content": {"type": "string"}}}}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/crawlers": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_crawlers", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/crawlers", "tags": ["Analytics"], "summary": "See crawler and AI agent visits to a website", "description": "Visits by search engine crawlers and AI agents, from imported server logs.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/insights": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_insights", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/insights", "tags": ["Analytics"], "summary": "Get what changed on a website", "description": "Plain-language notes on what moved most in the date range and why.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/people/{visitor_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_visitor_id"}], "get": {"operationId": "get_analytics_visitor", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/people/{visitor_id}", "tags": ["Analytics"], "summary": "Get one visitor's timeline", "description": "Everything one visitor did on the website, in order. Take the visitor ID from a segment or a recording.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/lifecycle": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_lifecycle", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/lifecycle", "tags": ["Analytics"], "summary": "Get a website's visitor lifecycle", "description": "Each week's new, returning, resurrected and dormant visitors.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/stickiness": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_stickiness", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/stickiness", "tags": ["Analytics"], "summary": "Get how often a website's visitors come back", "description": "How many visitors were active on one, two, three or more days of each week.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/server-keys": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "list_analytics_server_keys", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/server-keys", "tags": ["Analytics"], "summary": "List a website's server keys", "description": "The keys servers use to send events and logs to Analytics. Only each key's first characters are shown.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "change_analytics_server_keys", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/server-keys", "tags": ["Analytics"], "summary": "Create or revoke a server key", "description": "Send `name` to make a key (its secret is returned once), or `revoke` with a key's first characters to revoke it.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"name": {"type": "string", "description": "A name for a new key."}, "revoke": {"type": "string", "description": "The first characters of the key to revoke."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/funnel": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "post": {"operationId": "query_analytics_funnel", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/funnel", "x-read-only": true, "tags": ["Analytics"], "summary": "Run a funnel on a website", "description": "How many visitors got through each step, in order. Send the steps, or the name of a saved funnel. A big question may come back still running, with a `query_id` for Get an Analytics query. It only reads.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"steps": {"type": "array", "description": "The steps, each a page or an event."}, "funnel": {"type": "string", "description": "Or the name of a saved funnel."}, "range": {"type": "string", "description": "The date range."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/exit-pages": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_exit_pages", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/exit-pages", "tags": ["Analytics"], "summary": "Get a website's exit pages", "description": "The pages visitors most often left from. A big question may come back still running, with a `query_id`.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/queries/{query_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_query_id"}], "get": {"operationId": "get_analytics_query", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/queries/{query_id}", "tags": ["Analytics"], "summary": "Get an Analytics query that was still running", "description": "The result of a funnel or exit-pages question that was not ready at first.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/ga4": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_ga4", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/ga4", "tags": ["Analytics"], "summary": "Get GA4's numbers for a website", "description": "What GA4 measured for the same website and dates (from a nightly copy), where GA4 is linked.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/audience": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_audience", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/audience", "tags": ["Analytics"], "summary": "Get GA4's audience breakdowns for a website", "description": "New against returning visitors, age, interests and other breakdowns only GA4 has, where GA4 is linked.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/data-check": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}], "get": {"operationId": "get_analytics_data_check", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/data-check", "tags": ["Analytics"], "summary": "Compare a website's numbers with GA4's", "description": "Our numbers and GA4's day by day, and anything that looks wrong, such as a tag missing from part of the site.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/sources/{source_name}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_source_name"}], "get": {"operationId": "get_analytics_source_panel", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/sources/{source_name}", "tags": ["Analytics"], "summary": "Get a connected source's panel for a website", "description": "The panel for one of the website's linked data sources (for example gsc, ads or shopify), read with your own access to it.", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/analytics/sites/{site_id}/reports/{report_name}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/analytics_site_id"}, {"$ref": "#/components/parameters/analytics_report_name"}], "get": {"operationId": "get_analytics_report", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "analytics", "x-service-path": "sites/{site_id}/reports/{report_name}", "tags": ["Analytics"], "summary": "Get a standard Analytics report", "description": "One of the standard reports: overview, channels, seo, ecommerce, ad_lift or attribution.\n\n`ad_lift` (Ad-driven lift): the direct visits, organic-search visits and branded searches the client's ads\nbrought, by ad channel (Google Ads YouTube, Display/Demand Gen/PMax, Search; Meta; MNTN TV) and by campaign\n(`by_channel`, `by_campaign`: extra visits, share, cost per extra visit, confidence), with `outcomes`,\n`flights`, `hourly` (TV) and, for websites that run Attribution, `came_back_after_click`. Ad accounts and\nsearch sources are read with your own access (a person's token). Optional query parameters: `outcomes`\n(comma-separated: direct, organic_search, brand_searches_google, brand_searches_google_ads,\nbrand_searches_microsoft), `brand_terms`, `exclude_terms`, `ads_customer_id`, `meta_ad_account_id`,\n`mntn_advertiser_id`, `ms_ads_account_id`, `site_url`, `include_hourly`, `baseline_days`, `after_days`; they\noverride the website's saved `ad_lift` Analytics settings for this request. When the answer has `retry`,\nMicrosoft is still building its report: call again in about 20 seconds with `ms_ads_report_id` from it.\nA correlation, not proof.\n\n`attribution` (websites that run Attribution): visits, unique people, devices, browsers and households,\nvisitors recognised after clearing cookies, form submits by channel, source and page, and direct or\norganic visits after an ad click, with compare.\n", "parameters": [{"$ref": "#/components/parameters/analytics_range"}, {"$ref": "#/components/parameters/analytics_start"}, {"$ref": "#/components/parameters/analytics_end"}, {"$ref": "#/components/parameters/date_compare"}, {"$ref": "#/components/parameters/date_compare_start"}, {"$ref": "#/components/parameters/date_compare_end"}, {"$ref": "#/components/parameters/date_match_weekday"}, {"$ref": "#/components/parameters/analytics_data"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/agencies/{agency_id}/optimizer-summary": {"parameters": [{"$ref": "#/components/parameters/agency_id"}], "get": {"operationId": "get_agency_optimizer_summary", "x-handler": "optimizer_agency_summary", "x-roles": ["admin", "agency_admin"], "tags": ["Website Optimizer"], "summary": "Get Website Optimizer's totals across an agency's clients", "description": "For every client of the agency that you can see and that has Website Optimizer on: its websites, open and new recommendations and alerts this week. The agency view of the Website Optimizer screen.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/access": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_optimizer_access", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "me", "tags": ["Website Optimizer"], "summary": "See what you may do in a client's Website Optimizer", "description": "Your role in the client as Website Optimizer sees it, and whether you may change things.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/summary": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_optimizer_summary", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "summary", "tags": ["Website Optimizer"], "summary": "Get a client's Website Optimizer totals", "description": "Websites, open and new recommendations, alerts this week and the top recommendation: the numbers on the client's Home page.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/people": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_optimizer_people", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "people", "tags": ["Website Optimizer"], "summary": "List the people who can get Website Optimizer emails", "description": "The client's people, for the weekly digest and alert lists.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_optimizer_sites", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites", "x-list": {"key": "sites"}, "tags": ["Website Optimizer"], "summary": "List a client's websites in Website Optimizer", "description": "The websites being checked, and under `addable` the client's tracked websites that could be added.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}, "parameters": [{"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}]}, "post": {"operationId": "add_optimizer_site", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites", "tags": ["Website Optimizer"], "summary": "Add a website to Website Optimizer", "description": "Admins only. Send `site_id` for one of the client's tracked websites, or `url` for any address. You become the person its daily checks run as. The first check starts at once.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"site_id": {"type": "string", "description": "A tracked website's ID."}, "url": {"type": "string", "description": "Or any website address."}, "name": {"type": "string", "description": "A name to show."}}}}}}, "responses": {"201": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "get": {"operationId": "get_optimizer_site", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}", "tags": ["Website Optimizer"], "summary": "Get a website in Website Optimizer", "description": "Its goals, the pages checked, its scores and when it was last checked.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "update_optimizer_site", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}", "tags": ["Website Optimizer"], "summary": "Change a website in Website Optimizer", "description": "Admins only. Send only what changes: goals, name, pages, auto_pages, status, digest_email, alerts. Send the `version` you read so two people editing at once don't overwrite each other.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"goals": {"type": "object", "description": "What the website is for: primary, secondary, conversion_pages, markets, audience, priorities, business_type, notes."}, "name": {"type": "string", "description": "A name to show."}, "pages": {"type": "array", "description": "The pages to check."}, "auto_pages": {"type": "boolean", "description": "Pick the pages to check automatically."}, "status": {"type": "string", "description": "active or paused."}, "digest_email": {"type": "object", "description": "Who gets the weekly digest."}, "alerts": {"type": "object", "description": "Who is told about urgent problems, and how."}, "version": {"type": "integer", "description": "The version you read."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "remove_optimizer_site", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}", "tags": ["Website Optimizer"], "summary": "Remove a website from Website Optimizer", "description": "Admins only. Removes the website with its recommendations, scores and fixes.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/optimizer/sites/{site_id}/take-over": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "post": {"operationId": "take_over_optimizer_site", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/take-over", "tags": ["Website Optimizer"], "summary": "Make yourself the person a website's checks run as", "description": "Admins only. The daily check reads the client's data with one person's access; this makes that person you.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/webhook-secret": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "get": {"operationId": "get_optimizer_webhook_secret", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/webhook-secret", "tags": ["Website Optimizer"], "summary": "Get a website's alert webhook signing secret", "description": "Admins only. The secret that signs the alerts Website Optimizer posts to your webhook.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/check": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "post": {"operationId": "check_optimizer_site", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/check", "tags": ["Website Optimizer"], "summary": "Check a website now", "description": "Admins only. Starts a check; results arrive in a few minutes. At most once an hour.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/recommendations": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "get": {"operationId": "list_optimizer_recommendations", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/recommendations", "tags": ["Website Optimizer"], "summary": "List a website's recommendations", "description": "What to fix, most valuable first, each with why it matters, the pages it is on and the steps to fix it.", "parameters": [{"name": "status", "in": "query", "description": "open (default), all, or one status such as done or dismissed.", "schema": {"type": "string"}}, {"name": "category", "in": "query", "description": "Only one category, for example seo or speed.", "schema": {"type": "string"}}, {"name": "limit", "in": "query", "description": "How many to return (default 50).", "schema": {"type": "integer"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/recommendations/{recommendation_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}, {"$ref": "#/components/parameters/optimizer_recommendation_id"}], "put": {"operationId": "update_optimizer_recommendation", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/recommendations/{recommendation_id}", "tags": ["Website Optimizer"], "summary": "Change a recommendation's status", "description": "Admins only. Mark a recommendation done, dismissed or open again, with a reason.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"status": {"type": "string", "description": "The new status."}, "reason": {"type": "string", "description": "Why, in a few words."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/recommendations/{recommendation_id}/fix": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}, {"$ref": "#/components/parameters/optimizer_recommendation_id"}], "get": {"operationId": "preview_optimizer_fix", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/recommendations/{recommendation_id}/fix", "tags": ["Website Optimizer"], "summary": "Preview a one-click fix", "description": "Admins only. What a one-click fix would change on each page, before applying it. Only some recommendations can be fixed this way (`can_fix`).", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "apply_optimizer_fix", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/recommendations/{recommendation_id}/fix", "tags": ["Website Optimizer"], "summary": "Apply a one-click fix", "description": "Admins only. Applies the fix to the pages you send, through the website's connected platform. It can be undone.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"pages": {"type": "array", "description": "The pages to fix, each {url, values}, from the preview."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/recommendations/{recommendation_id}/test": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}, {"$ref": "#/components/parameters/optimizer_recommendation_id"}], "get": {"operationId": "draft_optimizer_test", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/recommendations/{recommendation_id}/test", "tags": ["Website Optimizer"], "summary": "Draft an A/B test for a recommendation", "description": "Admins only. A ready-made A/B test that would prove whether the recommendation helps, to review before saving.", "parameters": [{"name": "goal_event", "in": "query", "description": "The event the test is judged by.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "save_optimizer_test", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/recommendations/{recommendation_id}/test", "tags": ["Website Optimizer"], "summary": "Save a recommendation's A/B test in Analytics", "description": "Admins only. Saves the test as a draft in Analytics (the A/B tests add-on); start it on the admin page.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"experiment": {"type": "object", "description": "The test, as drafted."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/fixes": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "get": {"operationId": "list_optimizer_fixes", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/fixes", "tags": ["Website Optimizer"], "summary": "List the fixes applied to a website", "description": "One-click fixes that were applied, newest first, and whether each was undone.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/fixes/{fix_id}/undo": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}, {"$ref": "#/components/parameters/optimizer_fix_id"}], "post": {"operationId": "undo_optimizer_fix", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/fixes/{fix_id}/undo", "tags": ["Website Optimizer"], "summary": "Undo a fix", "description": "Admins only. Puts the pages back as they were before the fix.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/report": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "get": {"operationId": "get_optimizer_report", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/report", "tags": ["Website Optimizer"], "summary": "Get a website's one-page report", "description": "Scores, what changed, the top recommendations and what was fixed, as data.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/report/pdf": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "post": {"operationId": "create_optimizer_report_pdf", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/report/pdf", "tags": ["Website Optimizer"], "summary": "Make a website's report as a PDF", "description": "Returns a download link (`url`) that works for five minutes, with a file `name`.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/scores": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "get": {"operationId": "get_optimizer_scores", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/scores", "tags": ["Website Optimizer"], "summary": "Get a website's score history", "description": "Its scores day by day, for the whole site or one page.", "parameters": [{"name": "page", "in": "query", "description": "One page; leave out for the site.", "schema": {"type": "string"}}, {"name": "days", "in": "query", "description": "How many days back.", "schema": {"type": "integer"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/audit": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "get": {"operationId": "get_optimizer_audit", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/audit", "tags": ["Website Optimizer"], "summary": "Get the newest audit of a page", "description": "Everything the last check found on one page.", "parameters": [{"name": "page", "in": "query", "description": "The page's address.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/optimizer/sites/{site_id}/alerts": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/optimizer_site_id"}], "get": {"operationId": "list_optimizer_alerts", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "optimizer", "x-service-path": "sites/{site_id}/alerts", "x-list": {"key": "alerts"}, "tags": ["Website Optimizer"], "summary": "List a website's alerts", "description": "Urgent problems Website Optimizer noticed, newest first.", "parameters": [{"name": "days", "in": "query", "description": "How many days back.", "schema": {"type": "integer"}}, {"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/overview": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_conversions_overview", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/overview", "tags": ["Offline Conversions"], "summary": "Get a client's offline conversions overview", "description": "Conversions sent, skipped and failed per day and per platform, the value sent, how many were sent of those tried, and anything that needs attention.", "parameters": [{"name": "days", "in": "query", "required": false, "description": "Days back (1-90, default 30).", "schema": {"type": "integer"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/catalog": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_conversions_catalog", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/catalog", "tags": ["Offline Conversions"], "summary": "Get what conversion rules can use", "description": "Event types, fields and operators for conditions, value modes, platforms (with their test modes), and the client's ad connections and data sources.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/rules": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_conversion_rules", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/rules", "tags": ["Offline Conversions"], "summary": "List a client's conversion rules", "description": "Rules (draft, on, paused) with their last 7 days' counts, and the Attribution mappings still being sent (read-only rules whose IDs start with m-).", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "create_conversion_rule", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/rules", "tags": ["Offline Conversions"], "summary": "Create a conversion rule", "description": "Admins only. Saves a draft: nothing is sent until the rule is turned on. Send `rule`: {name, description?, trigger: {events, sources?}, conditions?: {match: all|any, items: [{field, op, value}]}, value?: {mode, amount, min, max, currency}, hold_hours?, limits?: {daily_sends, daily_value}, destinations: [{platform, connection, account_id, action}]}. Get conversions catalog lists the choices.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"rule": {"type": "object", "description": "The rule."}}}}}}, "responses": {"201": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/rules/{rule_id}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/conversions_rule_id"}], "get": {"operationId": "get_conversion_rule", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/rules/{rule_id}", "tags": ["Offline Conversions"], "summary": "Get a conversion rule", "description": "One rule, its versions and today's use of its daily limits.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "update_conversion_rule", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/rules/{rule_id}", "tags": ["Offline Conversions"], "summary": "Change a conversion rule", "description": "Admins only. Send the whole `rule` and the `version` you read, so two people editing at once don't overwrite each other. An active rule stays on.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"rule": {"type": "object", "description": "The rule."}, "version": {"type": "integer", "description": "The version you read."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "delete": {"operationId": "delete_conversion_rule", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/rules/{rule_id}", "tags": ["Offline Conversions"], "summary": "Delete a conversion rule", "description": "Admins only. What it already sent stays in the send log.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/conversions/rules/{rule_id}/status": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/conversions_rule_id"}], "post": {"operationId": "set_conversion_rule_status", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/rules/{rule_id}/status", "tags": ["Offline Conversions"], "summary": "Turn a conversion rule on or pause it", "description": "Admins only. `status`: active (turns it on: matching events are sent from now on; check its preview first), paused or draft. Turning a rule on works only from the admin page or this API, never from AI apps.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"status": {"type": "string", "description": "active, paused or draft."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/rules/{rule_id}/preview": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/conversions_rule_id"}], "get": {"operationId": "preview_conversion_rule", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/rules/{rule_id}/preview", "tags": ["Offline Conversions"], "summary": "Preview a conversion rule", "description": "Dry run, nothing is sent: what the rule would send over the last days per destination, the value, what it would match on, why other events don't match, and samples with shortened hashes.", "parameters": [{"name": "days", "in": "query", "required": false, "description": "Days back (1-90, default 30).", "schema": {"type": "integer"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/preview": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "preview_conversion_rule_draft", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/preview", "x-read-only": true, "tags": ["Offline Conversions"], "summary": "Preview a draft conversion rule", "description": "Dry run of a rule that isn't saved (`rule`, `days`). Nothing is sent or saved.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"rule": {"type": "object", "description": "The rule."}, "days": {"type": "integer", "description": "Days back (1-90)."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/rules/{rule_id}/test": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/conversions_rule_id"}], "post": {"operationId": "test_conversion_rule", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/rules/{rule_id}/test", "tags": ["Offline Conversions"], "summary": "Send a test conversion", "description": "Admins only. Sends the newest matching event of the last 30 days in each platform's test mode (Google checks it without recording, Meta shows it under Test events with the saved test code; Microsoft has no test mode). Not billed.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/mappings/{rule_id}/convert": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/conversions_rule_id"}], "post": {"operationId": "convert_conversion_mapping", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/mappings/{rule_id}/convert", "tags": ["Offline Conversions"], "summary": "Convert an Attribution mapping to a rule", "description": "Admins only. The mapping (ID starting with m-) becomes an active rule and keeps sending; nothing it sent is sent again.", "responses": {"201": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/log": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_conversion_sends", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/log", "tags": ["Offline Conversions"], "summary": "List sent conversions", "description": "The send log, newest first: sent, skipped (and why), failed, held (waiting for the rule's hold time) and test sends. No contact details.", "parameters": [{"name": "status", "in": "query", "required": false, "description": "sent, skipped, failed, held or test.", "schema": {"type": "string"}}, {"name": "rule_id", "in": "query", "required": false, "description": "Only this rule.", "schema": {"type": "string"}}, {"name": "platform", "in": "query", "required": false, "description": "Only this platform.", "schema": {"type": "string"}}, {"name": "limit", "in": "query", "required": false, "description": "At most this many (1-500, default 100).", "schema": {"type": "integer"}}, {"name": "cursor", "in": "query", "required": false, "description": "next_cursor from the previous page.", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/log/{log_id}/send-again": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/conversions_log_id"}], "post": {"operationId": "send_conversion_again", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/log/{log_id}/send-again", "tags": ["Offline Conversions"], "summary": "Send a failed conversion again", "description": "Admins only. Retries one failed send from the log.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/backfill": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "backfill_conversions", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/backfill", "tags": ["Offline Conversions"], "summary": "Send past events", "description": "Admins only. Runs the rules that are on (or one `rule_id`) over stored events of the last `days` (1-90). Events already sent are never sent again. Runs in the background.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"days": {"type": "integer", "description": "Days back (1-90)."}, "rule_id": {"type": "string", "description": "Only this rule."}}}}}}, "responses": {"202": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/audit": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_conversions_changes", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/audit", "tags": ["Offline Conversions"], "summary": "List changes to offline conversions", "description": "Who changed which rule or setting, how (admin page, Admin API, AI app) and when, with before and after.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/settings": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_conversions_settings", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/settings", "tags": ["Offline Conversions"], "summary": "Get offline conversions settings", "description": "Whether sending is paused for the client, whether only visitors who said yes to ads are sent, and whether a Meta test code is saved.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "put": {"operationId": "update_conversions_settings", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/settings", "tags": ["Offline Conversions"], "summary": "Change offline conversions settings", "description": "Admins only. Send only what changes: paused, require_consent, test_event_code.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"paused": {"type": "boolean", "description": "Pause all sending."}, "require_consent": {"type": "boolean", "description": "Only send visitors who said yes to ads."}, "test_event_code": {"type": "string", "description": "Meta Events Manager test code."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/destination-secrets": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_conversions_destination_secrets", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/destination-secrets", "tags": ["Offline Conversions"], "summary": "Get which GA4 streams have an API secret", "description": "Per GA4 measurement ID used by a rule: whether its Measurement Protocol API secret is saved. Secrets are never shown.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/destination-secrets/ga4": {"parameters": [{"$ref": "#/components/parameters/client"}], "put": {"operationId": "set_conversions_ga4_secret", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/destination-secrets/ga4", "tags": ["Offline Conversions"], "summary": "Save a GA4 Measurement Protocol API secret", "description": "Admins only. `measurement_id` (G-...) and `api_secret` (empty removes it). Sealed and never shown again.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"measurement_id": {"type": "string", "description": "The web stream's measurement ID."}, "api_secret": {"type": "string", "description": "The API secret."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/suppressions": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "list_conversions_suppressions", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/suppressions", "tags": ["Offline Conversions"], "summary": "List the do-not-send list", "description": "People who must never be sent to ad platforms (privacy requests, opt-outs). Only SHA-256 hashes are kept.", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}, "post": {"operationId": "add_conversions_suppressions", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/suppressions", "tags": ["Offline Conversions"], "summary": "Add people to the do-not-send list", "description": "Admins only. `emails` and `phones` are hashed before anything is stored; or send `hashes` (SHA-256 hex). `reason`: privacy_request, opt_out or other.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"emails": {"type": "array", "items": {"type": "string"}, "description": "Email addresses."}, "phones": {"type": "array", "items": {"type": "string"}, "description": "Phone numbers."}, "hashes": {"type": "array", "items": {"type": "string"}, "description": "SHA-256 hashes."}, "reason": {"type": "string", "description": "privacy_request, opt_out or other."}}}}}}, "responses": {"201": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/suppressions/{hash}": {"parameters": [{"$ref": "#/components/parameters/client"}, {"$ref": "#/components/parameters/conversions_hash"}], "delete": {"operationId": "remove_conversions_suppression", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/suppressions/{hash}", "tags": ["Offline Conversions"], "summary": "Remove someone from the do-not-send list", "description": "Admins only. Removes one hash.", "responses": {"default": {"$ref": "#/components/responses/Error"}, "204": {"description": "Done. No content."}}}}, "/clients/{client}/conversions/assistant": {"parameters": [{"$ref": "#/components/parameters/client"}], "post": {"operationId": "suggest_conversion_rules", "x-handler": "service_change", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/assistant", "tags": ["Offline Conversions"], "summary": "Ask the conversions assistant", "description": "Admins only. Describe a `goal`; the assistant studies the client's events, previews ideas and saves draft rules (never turned on) with its reasoning. Needs AI Services on; tokens are billed there.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "properties": {"goal": {"type": "string", "description": "The goal in plain words."}, "save_drafts": {"type": "boolean", "description": "Save the drafts (default true)."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}, "/clients/{client}/conversions/summary": {"parameters": [{"$ref": "#/components/parameters/client"}], "get": {"operationId": "get_conversions_summary", "x-handler": "service_read", "x-roles": ["everyone"], "x-service": "conversions", "x-service-path": "conversions/summary", "tags": ["Offline Conversions"], "summary": "Get a client's offline conversions summary", "description": "The last 7 days' totals, rules on and attention items (the Home tile).", "responses": {"200": {"$ref": "#/components/responses/Object"}, "default": {"$ref": "#/components/responses/Error"}}}}}, "components": {"securitySchemes": {"token": {"type": "http", "scheme": "bearer", "bearerFormat": "msat_...", "description": "A personal API token from the admin page (API & agents). It acts as you, with your current role."}}, "parameters": {"activity_actor": {"name": "actor", "in": "query", "description": "Only entries by this person (their email).", "schema": {"type": "string"}}, "activity_via": {"name": "via", "in": "query", "description": "Only entries from here (the admin page, this API, the API/MCP Gateway, the command line, the sign-in page, or the platform itself).", "schema": {"type": "string", "enum": ["page", "api", "gateway", "cli", "sign-in", "system"]}}, "activity_kind": {"name": "kind", "in": "query", "description": "Only changes, sign-ins, super admin log-ins to accounts (`session`) or deploys.", "schema": {"type": "string", "enum": ["change", "sign-in", "session", "deploy"]}}, "activity_action": {"name": "action", "in": "query", "description": "Only this action, by its stable name (e.g. `set_client_service`, `sign_in`, `tool:<gateway tool>`).", "schema": {"type": "string"}}, "activity_role": {"name": "role", "in": "query", "description": "Only entries made in this role (`super-admin` while logged into an account, `global` for other super admin work, `agency-admin`, `client-admin`, `client-user`).", "schema": {"type": "string"}}, "activity_q": {"name": "q", "in": "query", "description": "Text search over the summary, person, action, target, client and agency, ignoring case. Every word must appear.", "schema": {"type": "string", "maxLength": 100}}, "activity_since": {"name": "since", "in": "query", "description": "Only entries on or after this day (UTC).", "schema": {"type": "string", "format": "date"}}, "activity_until": {"name": "until", "in": "query", "description": "Only entries on or before this day (UTC).", "schema": {"type": "string", "format": "date"}}, "summary_tiles": {"name": "tiles", "in": "query", "required": false, "description": "Only these tiles, comma-separated keys (e.g. `gateway,attribution`).", "schema": {"type": "string"}}, "summary_lazy": {"name": "lazy", "in": "query", "required": false, "description": "`skip` lists the slower tiles as `pending` without reading them.", "schema": {"type": "string", "enum": ["skip"]}}, "usage_days": {"name": "days", "in": "query", "description": "How many days to show, today included (1 to 90).", "schema": {"type": "integer", "minimum": 1, "maximum": 90, "default": 30}}, "client": {"name": "client", "in": "path", "required": true, "schema": {"$ref": "#/components/schemas/ClientId"}}, "privacyRequest": {"name": "request", "in": "path", "required": true, "description": "The privacy request's ID (16 hex characters)", "schema": {"type": "string", "pattern": "^[0-9a-f]{16}$"}}, "agency_id": {"name": "agency_id", "in": "path", "required": true, "schema": {"$ref": "#/components/schemas/AgencyId"}}, "analytics_site_id": {"name": "site_id", "in": "path", "required": true, "description": "The website's ID (MS-XXXXXXXX), from List a client's websites with Analytics on.", "schema": {"type": "string", "pattern": "^MS-[A-Z0-9]{8}$"}}, "analytics_dimension": {"name": "dimension", "in": "path", "required": true, "description": "What to group by: channel, source, medium, source_medium, campaign, content, term, page, entry_page, referrer, click_id, country, region, city, device, browser, os, language or hostname.", "schema": {"type": "string", "pattern": "^[a-z_]{1,30}$"}}, "analytics_export_id": {"name": "export_id", "in": "path", "required": true, "description": "The export's ID, from Export a website's raw events.", "schema": {"type": "string", "pattern": "^[0-9a-f-]{36}$"}}, "analytics_query_id": {"name": "query_id", "in": "path", "required": true, "description": "The query's ID, from the answer that was still running.", "schema": {"type": "string", "pattern": "^[0-9a-f-]{36}$"}}, "analytics_replay_day": {"name": "day", "in": "path", "required": true, "description": "The recording's day (yyyy-mm-dd), from List a website's session recordings.", "schema": {"type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"}}, "analytics_replay_id": {"name": "replay_id", "in": "path", "required": true, "description": "The recording's ID.", "schema": {"type": "string", "pattern": "^[0-9a-f]{16,32}$"}}, "analytics_suggestion_id": {"name": "suggestion_id", "in": "path", "required": true, "description": "A suggested smart event's ID (c and 10 hex digits).", "schema": {"type": "string", "pattern": "^c[0-9a-f]{10}$"}}, "analytics_experiment_key": {"name": "experiment_key", "in": "path", "required": true, "description": "The experiment's key.", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_.:-]{1,60}$"}}, "analytics_visitor_id": {"name": "visitor_id", "in": "path", "required": true, "description": "The visitor's ID.", "schema": {"type": "string", "pattern": "^[0-9a-f]{24,64}$"}}, "analytics_source_name": {"name": "source_name", "in": "path", "required": true, "description": "The linked source, for example gsc, ads or shopify.", "schema": {"type": "string", "pattern": "^[a-z0-9_]{1,30}$"}}, "analytics_report_name": {"name": "report_name", "in": "path", "required": true, "description": "overview, channels, seo, ecommerce, ad_lift or attribution.", "schema": {"type": "string", "pattern": "^[a-z_]{1,30}$"}}, "analytics_range": {"name": "range", "in": "query", "description": "The date range: today, yesterday, this_week, last_week, last_7_days, last_14_days, last_28_days (default), last_30_days, last_90_days, this_month, last_month, this_quarter, last_quarter, this_year, last_year, last_12_months or custom (with start and end). Whole days; 'last N days' ends yesterday.", "schema": {"type": "string"}}, "analytics_start": {"name": "start", "in": "query", "description": "With range custom: the first day, yyyy-mm-dd.", "schema": {"type": "string"}}, "analytics_end": {"name": "end", "in": "query", "description": "With range custom: the last day, yyyy-mm-dd.", "schema": {"type": "string"}}, "date_compare": {"name": "compare", "in": "query", "description": "Compare with another period: none (default; the overview defaults to previous), previous (the period just before, of the same length; this_month/quarter/year compare the same days of the one before, last_month/quarter/year the whole one before), year (the same dates a year earlier) or custom (with compare_start and compare_end). The answer then adds compare_period, previous and change (percent) on its totals and on each row.", "schema": {"type": "string", "enum": ["none", "previous", "year", "custom"]}}, "date_compare_start": {"name": "compare_start", "in": "query", "description": "With compare custom: the first compared day, yyyy-mm-dd.", "schema": {"type": "string", "format": "date"}}, "date_compare_end": {"name": "compare_end", "in": "query", "description": "With compare custom: the last compared day, yyyy-mm-dd.", "schema": {"type": "string", "format": "date"}}, "date_match_weekday": {"name": "match_weekday", "in": "query", "description": "1 to line compared days up by day of the week (previous: whole weeks back; year: 52 weeks back).", "schema": {"type": "string", "enum": ["0", "1"]}}, "analytics_data": {"name": "data", "in": "query", "description": "Whose numbers: ours, ga4 or blended (the last two need GA4 linked). Without it: your saved choice (Get your Analytics preferences), else blended when GA4 is linked to the website, else ours. The answer's `data` says which.", "schema": {"type": "string"}}, "conversions_rule_id": {"name": "rule_id", "in": "path", "required": true, "description": "A conversion rule's ID (or a mapping's, starting with m-), from List a client's conversion rules.", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$"}}, "conversions_log_id": {"name": "log_id", "in": "path", "required": true, "description": "A send's ID from List sent conversions.", "schema": {"type": "string", "pattern": "^\\d{8}T\\d{6}Z-[0-9a-f]{8}$"}}, "conversions_hash": {"name": "hash", "in": "path", "required": true, "description": "A do-not-send entry's SHA-256 hash.", "schema": {"type": "string", "pattern": "^[0-9a-fA-F]{64}$"}}, "optimizer_site_id": {"name": "site_id", "in": "path", "required": true, "description": "The website's ID in Website Optimizer, from List a client's websites in Website Optimizer.", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$"}}, "optimizer_recommendation_id": {"name": "recommendation_id", "in": "path", "required": true, "description": "The recommendation's ID.", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$"}}, "optimizer_fix_id": {"name": "fix_id", "in": "path", "required": true, "description": "The fix's ID, from List the fixes applied to a website.", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$"}}, "dashboard_id": {"name": "dashboard_id", "in": "path", "required": true, "description": "The dashboard's ID, from List dashboards.", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$"}}, "period": {"name": "period", "in": "path", "required": true, "description": "The invoice's month.", "schema": {"type": "string", "pattern": "^\\d{4}-\\d{2}$", "example": "2026-11"}}, "card_token": {"name": "token", "in": "path", "required": true, "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,63}$"}}, "session_id": {"name": "session_id", "in": "path", "required": true, "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,63}$"}}, "username": {"name": "username", "in": "path", "required": true, "description": "The person's username (from list_client_users or list_agency_users), not their email.", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,63}$"}}, "service": {"name": "service", "in": "path", "required": true, "schema": {"type": "string", "enum": ["ga4", "gtm", "gsc", "gbp", "merchant", "ads", "adsense", "datamanager", "pagespeed", "shopify", "meta_ads", "facebook", "instagram", "meta_business", "wordpress", "callrail", "microsoft_ads", "bing_webmaster", "supermetrics", "salesforce", "mntn", "ringex", "ringcx", "netsuite", "hubspot", "snapfulfil", "techship", "mailchimp", "gorgias", "klaviyo"]}}, "source": {"name": "source", "in": "path", "required": true, "description": "The data source's name.", "schema": {"$ref": "#/components/schemas/Name"}}, "connection_name": {"name": "name", "in": "path", "required": true, "schema": {"$ref": "#/components/schemas/Name"}}, "provider": {"name": "provider", "in": "path", "required": true, "description": "The provider ID from get_cliententity, for example oidc-1a2b3c4d5e.", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,63}$"}}, "social_provider": {"name": "provider", "in": "path", "required": true, "schema": {"type": "string", "enum": ["Google", "Microsoft", "SignInWithApple", "Facebook", "LoginWithAmazon"]}}, "domain": {"name": "domain", "in": "path", "required": true, "schema": {"type": "string", "pattern": "^[a-z0-9.-]{4,253}$"}}, "limit": {"name": "limit", "in": "query", "description": "Most items to return (default 100, max 500).", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}}, "cursor": {"name": "cursor", "in": "query", "description": "Where to continue: the `next_cursor` from the previous page.", "schema": {"type": "string"}}}, "responses": {"Error": {"description": "An error. `error` is a stable code: `bad_request` (400), `unauthorized` (401, missing or\nunknown token), `payment_failed` (402, a card was declined), `forbidden` (403, your role or a\nread-only token doesn't allow it), `not_found` (404), `method_not_allowed` (405), `version_retired` (410,\na retired API version). 429 means too many requests (sent by the rate limiter, without a code). `request_id` identifies the call if you\ncontact support.\n", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "Object": {"description": "The result", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}, "Revoked": {"description": "Revoked", "content": {"application/json": {"schema": {"type": "object", "properties": {"revoked": {"type": "string"}}}}}}, "ShopifyTracking": {"description": "Whether the store's tracking pixel is on, and the tracked website it sends to", "content": {"application/json": {"schema": {"type": "object", "properties": {"status": {"type": "string", "enum": ["on", "claimed_elsewhere", "unavailable", "service_off", "missing_permission"]}, "message": {"type": "string"}, "domain": {"type": "string", "description": "The store's main domain"}, "site": {"type": "object", "nullable": true, "additionalProperties": true, "description": "The tracked website"}, "pixel": {"type": "object", "nullable": true, "additionalProperties": true}}}}}}, "ConnectLink": {"description": "A link to open in a browser", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "format": "uri"}, "expires_at": {"type": "string"}, "scopes": {"type": "array", "items": {"type": "string"}}}}}}}}, "schemas": {"UsageDay": {"type": "object", "properties": {"day": {"type": "string", "format": "date", "example": "2026-11-08"}, "count": {"type": "integer", "description": "Successful (billed) calls."}, "by_service": {"type": "object", "additionalProperties": {"type": "integer"}, "example": {"ga4": 12, "gtm": 3}}, "failed": {"type": "integer", "description": "Failed calls (not billed)."}, "failed_by_service": {"type": "object", "additionalProperties": {"type": "integer"}}}}, "ClientUsage": {"type": "object", "properties": {"client": {"type": "string"}, "start": {"type": "string", "format": "date"}, "end": {"type": "string", "format": "date"}, "days": {"type": "array", "items": {"$ref": "#/components/schemas/UsageDay"}}, "totals": {"type": "object", "description": "The same fields as a day, added up over the range (services busiest first).", "properties": {"count": {"type": "integer"}, "by_service": {"type": "object", "additionalProperties": {"type": "integer"}}, "failed": {"type": "integer"}, "failed_by_service": {"type": "object", "additionalProperties": {"type": "integer"}}}}, "month": {"type": "object", "properties": {"period": {"type": "string", "example": "2026-11"}, "count": {"type": "integer", "description": "Billed calls so far this month."}, "included": {"type": ["integer", "null"], "description": "Free calls per month included with the base fee."}}}, "services": {"type": "object", "additionalProperties": {"type": "string"}, "description": "Service names to titles."}}}, "AgencyUsage": {"type": "object", "properties": {"agency_id": {"type": "string"}, "start": {"type": "string", "format": "date"}, "end": {"type": "string", "format": "date"}, "count": {"type": "integer"}, "failed": {"type": "integer"}, "daily": {"type": "array", "items": {"type": "object", "properties": {"day": {"type": "string", "format": "date"}, "count": {"type": "integer"}}}}, "clients": {"type": "array", "items": {"type": "object", "properties": {"client": {"type": "string"}, "name": {"type": "string"}, "count": {"type": "integer"}, "failed": {"type": "integer"}, "by_service": {"type": "object", "additionalProperties": {"type": "integer"}}, "daily": {"type": "array", "items": {"type": "integer"}, "description": "Calls per day", "oldest first.": null}}}}, "services": {"type": "object", "additionalProperties": {"type": "string"}}}}, "HomeSummary": {"type": "object", "properties": {"client": {"type": "string", "description": "On a client's summary."}, "agency_id": {"type": "string", "description": "On an agency's summary."}, "name": {"type": "string"}, "home": {"type": "string", "enum": ["client", "agency"]}, "period": {"type": "string", "description": "This month (UTC) as YYYY-MM."}, "days": {"type": "integer", "description": "How many days the daily series cover (today last)."}, "services_on": {"type": "array", "items": {"type": "string"}}, "tiles": {"type": "array", "items": {"type": "object", "properties": {"key": {"type": "string", "description": "gateway, attribution, sources, live_events, dashboards, ai, client_bill (client), trial and agency_bill (agency); more as services add tiles."}, "title": {"type": "string"}, "service": {"type": "string", "description": "The paid service the tile is about (\"\" if none)."}, "lazy": {"type": "boolean", "description": "The page fills this tile in after the rest."}, "pending": {"type": "boolean", "description": "Not read (lazy=skip)."}, "data": {"type": "object", "additionalProperties": true, "description": "The tile's numbers."}, "error": {"type": "string"}}}}}}, "ProfileDetails": {"type": "object", "properties": {"first_name": {"type": "string", "maxLength": 100}, "last_name": {"type": "string", "maxLength": 100}, "name": {"type": "string", "description": "Older way to send first and last name: split at the first space."}, "phone": {"type": "string", "maxLength": 40, "description": "Saved in international form (+15551234567). Start with + and the country code, or give a local number and we use the country's code.\n"}, "line1": {"type": "string", "maxLength": 200, "description": "Street address"}, "line2": {"type": "string", "maxLength": 200, "description": "Apartment", "suite": null, "etc. (optional)": null}, "city": {"type": "string", "maxLength": 100}, "region": {"type": "string", "maxLength": 100, "description": "State", "province or county (optional).": null}, "postal_code": {"type": "string", "maxLength": 20}, "country": {"type": "string", "description": "Two-letter country code (ISO 3166)."}, "time_zone": {"type": "string", "maxLength": 64, "description": "The time zone the admin page shows dates and times in, as an IANA name such as America/Chicago. Empty for automatic (the browser's time zone). Can be sent on its own. The API itself always gives times in UTC.\n"}}}, "Profile": {"type": "object", "properties": {"username": {"type": "string"}, "email": {"type": "string"}, "first_name": {"type": "string"}, "last_name": {"type": "string"}, "name": {"type": "string", "description": "First and last name together."}, "phone": {"type": "string", "description": "International form", "e.g. +15551234567": null}, "line1": {"type": "string"}, "line2": {"type": "string"}, "city": {"type": "string"}, "region": {"type": "string"}, "postal_code": {"type": "string"}, "country": {"type": "string"}, "missing": {"type": "array", "items": {"type": "string"}, "description": "Required details that are still empty. The admin page asks for them before anything else."}, "complete": {"type": "boolean", "description": "Every required detail is filled in."}, "sign_in": {"type": "string", "enum": ["password", "external"], "description": "external: company or social sign-in"}, "authenticator_app": {"type": "boolean", "description": "An authenticator app (MFA) is set up."}, "passkey_sign_in": {"type": "boolean", "description": "You can sign in with a passkey."}, "created_at": {"type": "string"}, "time_zone": {"type": "string", "description": "Your time zone for the admin page (IANA name); empty means automatic."}}}, "PrivacyRequest": {"type": "object", "description": "One visitor's request. `status`: verifying, verified, searching, found, working, ready, closed, refused or\nneeds_attention. `labels` describe what the visitor gave without showing an email or phone. `matches` are\nwhat the search found (`how`: exact or guessed); `selected` the keys chosen as the visitor's.\n", "properties": {"id": {"type": "string"}, "kind": {"type": "string", "enum": ["access", "delete", "correct", "stop"]}, "kind_label": {"type": "string"}, "status": {"type": "string"}, "channel": {"type": "string", "enum": ["client", "direct"]}, "received_at": {"type": "string", "format": "date-time"}, "due_at": {"type": "string", "format": "date-time"}, "extended": {"type": "boolean"}, "by_agent": {"type": "boolean"}, "labels": {"type": "array", "items": {"type": "string"}}, "identity": {"type": "object", "properties": {"method": {"type": "string"}, "label": {"type": "string"}, "checked": {"type": "boolean"}, "link_sent": {"type": "boolean"}, "reason": {"type": "string"}}}, "matches": {"type": "array", "items": {"type": "object", "properties": {"key": {"type": "string"}, "service": {"type": "string"}, "kind": {"type": "string"}, "id": {"type": "string"}, "how": {"type": "string", "enum": ["exact", "guessed"]}, "confidence": {"type": "string"}, "label": {"type": "string"}, "last_seen": {"type": "string"}, "count": {"type": "integer"}}}}, "selected": {"type": "array", "items": {"type": "string"}}, "found_counts": {"type": "object", "additionalProperties": {"type": "integer"}}, "counts": {"type": "object", "additionalProperties": true}, "warnings": {"type": "array", "items": {"type": "string"}}, "leftovers": {"type": "object", "additionalProperties": {"type": "integer"}}, "export_ready": {"type": "boolean"}, "export_expires_at": {"type": "string"}, "draft_answer": {"type": "string"}}}, "TrackingSettings": {"type": "object", "properties": {"id_scope": {"type": "string", "enum": ["client", "domain"], "default": "client"}, "retention_months": {"type": "integer", "minimum": 1, "maximum": 36, "default": 3}, "link_sources": {"type": "boolean", "default": true, "description": "Link data sources' records (Shopify orders, CallRail calls, Blazeo leads, WordPress form entries,\nSalesforce leads, contacts and opportunities, HubSpot contacts and form submissions) to the visitor behind them. Tool answers then carry an\n`attribution` block, and Look up lists a person's linked records.\n"}}}, "TagRules": {"type": "object", "properties": {"lowercase": {"type": "boolean", "default": true}, "default_channels": {"type": "boolean", "default": true}, "values": {"type": "array", "maxItems": 500, "items": {"type": "object", "required": ["field", "value", "to"], "properties": {"field": {"type": "string", "enum": ["source", "medium", "campaign", "term", "content"]}, "match": {"type": "string", "enum": ["exact", "contains", "starts", "regex"], "default": "exact"}, "value": {"type": "string"}, "to": {"type": "string"}, "note": {"type": "string"}}}}, "channels": {"type": "array", "maxItems": 50, "items": {"type": "object", "required": ["name", "when"], "properties": {"name": {"type": "string"}, "note": {"type": "string"}, "when": {"type": "array", "items": {"type": "object", "required": ["field", "value"], "properties": {"field": {"type": "string", "enum": ["source", "medium", "campaign", "term", "content", "channel"]}, "match": {"type": "string", "enum": ["exact", "contains", "starts", "regex"], "default": "exact"}, "value": {"type": "string"}}}}}}}}}, "ConversionSettings": {"type": "object", "properties": {"enabled": {"type": "boolean", "default": true}, "min_call_seconds": {"type": "integer", "minimum": 0, "maximum": 3600, "default": 60}, "mappings": {"type": "array", "maxItems": 30, "items": {"type": "object", "required": ["event", "platform", "connection", "account_id", "action"], "properties": {"id": {"type": "string", "description": "Kept when editing; made for new mappings"}, "event": {"type": "string", "enum": ["shopify_order", "woocommerce_order", "wordpress_form", "blazeo_lead", "callrail_call", "callrail_form", "hubspot_contact", "netsuite_lead", "netsuite_sale", "ringcentral_call", "salesforce_lead", "salesforce_won"]}, "platform": {"type": "string", "enum": ["google_ads", "meta", "microsoft_ads"]}, "connection": {"type": "string", "description": "The client's ad connection to send with"}, "account_id": {"type": "string"}, "action": {"type": "string"}, "value": {"type": "string", "default": "record", "description": "\"record\", a number, or empty"}, "currency": {"type": "string", "default": "USD"}, "source": {"type": "string", "description": "Only this data source (empty = all of that type)"}}}}}}, "TrackingSiteInput": {"type": "object", "description": "A site's settings. On create, domain is required; on update, send only what changes.", "properties": {"domain": {"type": "string", "example": "acme.com", "description": "The website's exact domain"}, "subdomains": {"type": "array", "maxItems": 20, "items": {"type": "string", "example": "www.acme.com"}, "description": "Other hosts of the domain the script should also run on. It runs on the domain and these only."}, "name": {"type": "string", "maxLength": 100}, "status": {"type": "string", "enum": ["active", "paused"]}, "debug": {"type": "boolean", "description": "Log what the script does to the browser console"}, "core_version": {"type": "string", "example": "latest", "description": "latest", "or a published version like v3": null}, "consent_mode": {"type": "string", "enum": ["none", "gcm", "cmp"], "description": "When other platforms' IDs may be read (see capture_ids). none: always. gcm: from Google Consent\nMode (analytics_storage; ad_storage and ad_user_data). cmp: from the IAB TCF or GPP banner, or\nthe page calling msa('consent', {analytics: true, ads: false}).\n"}, "touch_expiry_days": {"type": "integer", "minimum": 1, "maximum": 730}, "session_minutes": {"type": "integer", "minimum": 5, "maximum": 240}, "ignore_referrers": {"type": "array", "items": {"type": "string"}, "maxItems": 50}, "params": {"type": "object", "additionalProperties": {"type": "string"}, "description": "Extra URL parameters to capture, mapped to field names"}, "form_fill": {"type": "boolean"}, "form_touch": {"type": "string", "enum": ["default", "first_touch", "last_touch", "first_marketing", "last_marketing", "last_paid"], "description": "Which touch fills the plain fields: default (last non-direct, else last touch), first_marketing and last_marketing (first and last visit that wasn't direct), last_paid (last paid or tagged campaign)"}, "form_prefix": {"type": "string", "example": "ms_"}, "form_selectors": {"type": "object", "additionalProperties": {"type": "string"}, "description": "Field name to a CSS selector for forms that can't use the ms_ names"}, "count_submits": {"type": "boolean"}, "capture_contact": {"type": "boolean", "default": true, "description": "On form submit, hash (SHA-256, in the visitor's browser) the name, address, email and phone\nfields and send only the hashes. Email and phone hashes link a visitor's devices to one\nperson. Off: no form values are read. Values are normalized before hashing (see ms-attribution\nREADME, \"Normalizing before hashing\"), so they match other sources prepared the same way.\n"}, "keep_contact": {"type": "boolean", "default": true, "description": "With capture_contact: also keep a readable copy of the contact details, the identify() value,\nother tools' user IDs and email click tokens. It is encrypted in the visitor's browser with our\ncontact key; only the admin page decrypts it, when someone looks the person up.\n"}, "phone_country_code": {"type": "string", "default": "1", "example": "44", "description": "Calling code added to phone numbers typed without one before hashing (E.164). 1 = US/Canada."}, "capture_ids": {"type": "boolean", "default": true, "description": "Read the visitor and click IDs that other tags keep on the website (GA client and session,\nGoogle Ads, Meta _fbp/_fbc, TikTok, X, Microsoft, LinkedIn ...) and send them with the visit,\nso this data can later be joined with those platforms' own. Follows consent_mode: analytics\nIDs need analytics consent, ad IDs need ad consent, and ad IDs are skipped when the browser\nsends Global Privacy Control. Tag-held user IDs (Segment, Mixpanel) are sent only as hashes.\n"}, "share_ids": {"type": "boolean", "default": true, "description": "With capture_ids, also hand our browser and person IDs (ms_browser_id, ms_person_id) to the\nchosen platforms' tags that take a page's own ID: Clarity, Segment, Mixpanel, Amplitude, Heap,\nPostHog, HubSpot, Klaviyo and TikTok (external_id, SHA-256). Same consent rules. GA4 gets its\nuser_id from ga4_user_id. Meta, X, Microsoft Ads, LinkedIn, Pinterest, Snapchat, Reddit and\nCriteo have no browser call for this.\n"}, "id_platforms": {"type": "array", "description": "Which platforms' IDs to read (all by default).", "items": {"type": "string", "enum": ["google_analytics", "clarity", "segment", "mixpanel", "amplitude", "heap", "posthog", "hubspot", "klaviyo", "shopify", "google_ads", "meta", "tiktok", "x", "microsoft_ads", "linkedin", "pinterest", "snapchat", "reddit", "criteo"]}}, "ga4_measurement_id": {"type": "string", "example": "G-ABC1234"}, "ga4_user_id": {"type": "string", "enum": ["identified", "person", "browser", "off"], "default": "identified", "description": "Which ID becomes GA4's user_id. identified: our person ID once the person is known\n(msa('identify') or a form email), none before. Our IDs always go as user properties.\n"}, "google_user_data": {"type": "boolean", "default": true, "description": "Give Google Ads the SHA-256 hashed email and phone from forms and identify (enhanced conversions). Needs ad consent."}, "gtm_enabled": {"type": "boolean"}, "gtm_data_layer": {"type": "string"}, "gtm_event": {"type": "string"}}}, "TrackedSiteInput": {"type": "object", "properties": {"name": {"type": "string", "description": "Needed for an API"}, "status": {"type": "string", "enum": ["active", "paused"]}, "domain": {"type": "string", "example": "acme.com", "description": "Websites only"}, "subdomains": {"type": "array", "items": {"type": "string"}, "description": "Websites only. Extra hosts the tag runs on"}, "app_id": {"type": "string", "example": "com.acme.shop", "description": "Apps only. iOS bundle ID or Android package name"}, "services": {"type": "array", "items": {"type": "string", "example": "attribution"}, "description": "Services turned on for this site"}, "settings": {"type": "object", "description": "Each service's settings, by service. Attribution takes the TrackingSiteInput settings other than domain, subdomains, name and status.", "additionalProperties": {"type": "object"}}}}, "TrackedSite": {"allOf": [{"$ref": "#/components/schemas/TrackedSiteInput"}, {"type": "object", "properties": {"site_id": {"type": "string", "example": "MS-7KQ2M9XP"}, "client": {"type": "string"}, "kind": {"type": "string", "enum": ["website", "ios_app", "android_app", "api"]}, "kind_label": {"type": "string", "example": "Website"}, "running": {"type": "array", "items": {"type": "string"}, "description": "Services that actually run (on for the site and for the client)"}, "created_at": {"type": "string"}, "updated_at": {"type": "string"}, "updated_by": {"type": "string"}, "snippet": {"type": "string", "description": "Websites only. Paste into the website's <head> (on the custom tracking domain once it is active)"}, "script_url": {"type": "string"}, "tracking_domain": {"nullable": true, "description": "Websites only, on single-site answers. The custom tracking domain, if any", "allOf": [{"$ref": "#/components/schemas/TrackingDomain"}]}}}]}, "TrackingDomainAnswer": {"type": "object", "properties": {"available": {"type": "boolean", "description": "False until custom tracking domains are set up in this environment"}, "domain": {"nullable": true, "allOf": [{"$ref": "#/components/schemas/TrackingDomain"}]}}}, "TrackingDomain": {"type": "object", "properties": {"host": {"type": "string", "example": "t.acme.com"}, "site_id": {"type": "string"}, "client": {"type": "string"}, "status": {"type": "string", "enum": ["waiting_for_dns", "issuing_certificate", "connecting", "active", "failed"]}, "status_label": {"type": "string", "example": "Waiting for the nameserver records"}, "message": {"type": "string", "description": "What to do next", "in words": null}, "nameservers": {"type": "array", "items": {"type": "string", "example": "ns1.ns.metricsensei.com"}}, "records": {"type": "array", "description": "The NS records to add at the client's DNS provider", "items": {"type": "object", "properties": {"name": {"type": "string", "example": "t.acme.com"}, "type": {"type": "string", "example": "NS"}, "value": {"type": "string", "example": "ns1.ns.metricsensei.com"}}}}, "url": {"type": "string", "example": "https://t.acme.com"}, "created_at": {"type": "string"}, "updated_at": {"type": "string"}, "checked_at": {"type": "string"}, "live_at": {"type": "string"}, "created_by": {"type": "string"}}}, "TrackingSite": {"allOf": [{"$ref": "#/components/schemas/TrackingSiteInput"}, {"type": "object", "properties": {"site_id": {"type": "string", "example": "MS-7KQ2M9XP"}, "client": {"type": "string"}, "created_at": {"type": "string"}, "updated_at": {"type": "string"}, "updated_by": {"type": "string"}, "snippet": {"type": "string", "description": "Paste into the website's <head>"}, "script_url": {"type": "string"}}}]}, "VisitorLookup": {"type": "object", "properties": {"client": {"type": "string"}, "id_scope": {"type": "string", "enum": ["client", "domain"]}, "query": {"type": "object", "properties": {"kind": {"type": "string", "enum": ["id", "hash", "email", "phone", "ip", "platform_id"]}, "label": {"type": "string", "description": "What was searched for (an email or phone only as its hash)"}}}, "found": {"type": "boolean"}, "matched": {"type": "array", "description": "What the search term turned out to be", "items": {"type": "string", "enum": ["browser", "device", "person", "household", "connection"]}}, "matched_ids": {"type": "array", "description": "Other platforms' ID types the search term matched: cookie names such as `_ga` or `hubspotutk`, or `user ID` (a login user ID) and `kla_exchange_id` (a Klaviyo profile) for a person", "items": {"type": "string"}}, "people": {"type": "array", "items": {"type": "object", "properties": {"pid": {"type": "string"}, "scope": {"type": "string"}, "identified": {"type": "boolean"}, "browsers": {"type": "array", "items": {"type": "string"}}, "contact": {"type": "object", "description": "Hashed contact details from forms (em, ph, fn, ln, ad, ct, st, zp, co), up to 5 hashes each", "additionalProperties": {"type": "object", "properties": {"label": {"type": "string"}, "hashes": {"type": "array", "items": {"type": "string"}}}}}, "readable": {"type": "array", "description": "Readable contact details where the website keeps them, decrypted for this answer", "items": {"type": "object", "properties": {"key": {"type": "string", "example": "em"}, "label": {"type": "string", "example": "Email"}, "values": {"type": "array", "items": {"type": "string"}}}}}, "unreadable": {"type": "integer", "description": "Encrypted copies that couldn't be read"}}}}, "browsers": {"type": "array", "items": {"type": "object", "properties": {"bid": {"type": "string"}, "did": {"type": "string"}, "pid": {"type": "string"}, "hid": {"type": "string", "nullable": true}, "os": {"type": "string"}, "network": {"type": "string", "nullable": true, "description": "The home connection (IPv4 address or IPv6 /64)"}, "first_seen": {"type": "string", "format": "date-time"}, "last_seen": {"type": "string", "format": "date-time"}, "platform_ids": {"type": "object", "additionalProperties": {"type": "string"}, "description": "Other platforms' lasting IDs (_ga", "_fbp ...)": null}, "expired": {"type": "boolean"}}}}, "households": {"type": "array", "items": {"type": "object", "properties": {"hid": {"type": "string"}, "network": {"type": "string"}, "devices": {"type": "array", "items": {"type": "string"}}}}}, "connection": {"type": "object", "nullable": true, "description": "IP searches only. Devices seen on that home connection in the last 30 days, and its household."}, "sites": {"type": "array", "items": {"type": "object", "properties": {"site_id": {"type": "string"}, "domain": {"type": "string"}, "sessions": {"type": "integer"}, "submits": {"type": "integer"}, "first": {"type": "string"}, "last": {"type": "string"}}}}, "events": {"type": "array", "description": "The visit history", "newest first": null, "items": {"type": "object"}}, "touchpoints": {"type": "array", "description": "Where each session came from (channel", "source": null, "campaign": null, "click ID ...)": null, "items": {"type": "object"}}, "history": {"type": "object", "properties": {"searched": {"type": "boolean"}, "days": {"type": "integer"}, "truncated": {"type": "boolean", "description": "More than 500 history rows matched; only the newest are listed"}, "message": {"type": "string"}}}}}, "TrackingReport": {"type": "object", "properties": {"site": {"type": "string"}, "sites": {"type": "array", "items": {"type": "object"}}, "paused": {"type": "boolean"}, "message": {"type": "string"}, "start": {"type": "string"}, "end": {"type": "string"}, "interval": {"type": "string", "enum": ["hour", "day"]}, "counts": {"type": "object", "description": "sessions, refreshes, submits, new_browsers, returning, recovered, recovered_fuzzy, identified", "additionalProperties": {"type": "integer"}}, "uniques": {"type": "object", "description": "browsers, devices, people, households (estimates)", "additionalProperties": {"type": "integer"}}, "breakdowns": {"type": "object", "description": "Top values for channel, source, medium, campaign, click_id_type, landing_page, referrer, country, os, browser, device_type, submit_page, submit_channel, submit_source, core_version", "additionalProperties": {"type": "array", "items": {"type": "object", "properties": {"value": {"type": "string"}, "count": {"type": "integer"}}}}}, "series": {"type": "array", "items": {"type": "object", "properties": {"at": {"type": "string"}, "sessions": {"type": "integer"}, "submits": {"type": "integer"}, "browsers": {"type": "integer"}}}}}}, "ClientId": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,63}$", "example": "acme"}, "AgencyId": {"type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{0,62}$", "example": "bigco"}, "Agency": {"type": "object", "properties": {"agency_id": {"$ref": "#/components/schemas/AgencyId"}, "name": {"type": "string"}, "status": {"type": "string", "enum": ["active", "disabled"]}, "solo": {"type": "boolean", "description": "One customer with one client; hidden in the admin page."}, "clients": {"type": "array", "items": {"$ref": "#/components/schemas/ClientId"}}, "contact": {"$ref": "#/components/schemas/Contact"}, "trial_ends_at": {"type": "string", "description": "Free trial end (self-serve signups). Absent: no trial."}, "billing_starts_at": {"type": "string", "description": "Nothing before this is billed."}, "created_via": {"type": "string", "enum": ["signup", "invite"]}, "plan": {"type": "string", "description": "Billing plan this month (see `list_billing_plans`).", "example": "standard"}, "plan_label": {"type": "string", "example": "Standard"}, "plan_kind": {"type": "string", "enum": ["standard", "cost_plus", "free"]}, "plan_tier": {"type": "string", "description": "Standard plans: the tier every service is billed at (empty = core)."}, "next_plan": {"$ref": "#/components/schemas/NextPlan"}}}, "NextPlan": {"type": "object", "description": "A plan switch waiting to start. Absent when none is planned.", "properties": {"plan": {"type": "string"}, "label": {"type": "string"}, "from": {"type": "string", "description": "First month on the new plan.", "example": "2026-12"}}}, "AgencyPlan": {"type": "object", "properties": {"plan": {"type": "string", "example": "standard"}, "plan_label": {"type": "string", "example": "Standard"}, "plan_kind": {"type": "string", "enum": ["standard", "cost_plus", "free"]}, "plan_tier": {"type": "string", "description": "Standard plans: the tier every service is billed at (empty = core)."}, "next_plan": {"$ref": "#/components/schemas/NextPlan"}, "starts": {"type": "string", "description": "First month the change applies to.", "example": "2026-12"}}}, "Tier": {"type": "object", "properties": {"tier": {"type": "string", "example": "growth"}, "label": {"type": "string", "example": "Growth"}, "base_multiplier": {"type": "string", "description": "Multiplies the base fee.", "example": "2.5"}, "clients_multiplier": {"type": "string", "description": "Multiplies the clients the base fee includes.", "example": "4"}, "discount_pct": {"type": "string", "description": "Percent off client fees and usage prices.", "example": "10"}}}, "AgencyPlans": {"type": "object", "properties": {"plan": {"type": "string", "example": "standard"}, "plan_label": {"type": "string", "example": "Standard"}, "plan_kind": {"type": "string", "enum": ["standard", "cost_plus", "free"]}, "plan_tier": {"type": "string", "description": "Standard plans: the tier every service is billed at (empty = core)."}, "next_plan": {"$ref": "#/components/schemas/NextPlan"}, "can_change": {"type": "boolean"}, "changes_start": {"type": "string", "enum": ["this_month", "next_month"]}, "plans": {"type": "array", "items": {"$ref": "#/components/schemas/BillingPlan"}}}}, "BillingPlan": {"type": "object", "description": "How an agency is billed. `standard`: each service's monthly base fee per agency (with clients\nincluded), a fee per extra client, pooled usage allowances and usage above them per unit, with the\nagency's tier per service. `cost_plus`: only usage, every unit at our cost plus `markup_pct`\npercent, with no base fee. `free`: nothing is charged (usage still shows, at 0); only a Metric Sensei\nadmin can assign it.\n", "properties": {"plan": {"type": "string", "example": "standard"}, "label": {"type": "string", "example": "Standard"}, "kind": {"type": "string", "enum": ["standard", "cost_plus", "free"]}, "markup_pct": {"type": "string", "description": "cost_plus: percent added to our unit cost.", "example": "10"}, "selectable": {"type": "boolean", "description": "True if agency admins may pick it themselves."}, "tier": {"type": "string", "description": "Standard plans: the tier every service is billed at (empty = core).", "example": "growth"}, "description": {"type": "string"}}}, "Contact": {"type": "object", "description": "Who to reach and the postal address.", "properties": {"first_name": {"type": "string"}, "last_name": {"type": "string"}, "name": {"type": "string", "description": "First and last name together. Sent alone, it is split at the first space."}, "email": {"type": "string", "format": "email"}, "phone": {"type": "string"}, "line1": {"type": "string"}, "line2": {"type": "string"}, "city": {"type": "string"}, "region": {"type": "string", "description": "State", "province or county.": null}, "postal_code": {"type": "string"}, "country": {"type": "string", "description": "Two-letter country code (ISO 3166)."}}}, "AgencyUser": {"type": "object", "properties": {"username": {"type": "string"}, "email": {"type": "string"}, "status": {"type": "string"}, "enabled": {"type": "boolean"}, "manager": {"type": "boolean", "description": "Agency admin."}}}, "Invoice": {"type": "object", "properties": {"agency_id": {"$ref": "#/components/schemas/AgencyId"}, "period": {"type": "string", "example": "2026-11"}, "number": {"type": "string", "example": "MS-202611-BIGCO"}, "currency": {"type": "string"}, "status": {"type": "string", "enum": ["estimate", "open", "paid", "void"]}, "plan": {"type": "string", "description": "The billing plan the invoice was worked out with.", "example": "standard"}, "plan_label": {"type": "string", "example": "Standard"}, "issued_at": {"type": "string"}, "due_at": {"type": "string"}, "total": {"type": "string"}, "paid": {"type": "string"}, "balance": {"type": "string"}, "clients": {"type": "array", "description": "The lines grouped by client, with a subtotal each.", "items": {"type": "object", "properties": {"client": {"type": "string"}, "name": {"type": "string"}, "subtotal": {"type": "string"}, "lines": {"type": "array", "items": {"type": "object", "properties": {"service": {"type": "string"}, "kind": {"type": "string", "enum": ["base", "usage"]}, "description": {"type": "string"}, "metric": {"type": "string"}, "quantity": {"type": "string"}, "included": {"type": "string", "description": "Units the base fee included."}, "unit_price": {"type": "string"}, "amount": {"type": "string"}}}}}}}, "payments": {"type": "array", "items": {"type": "object", "properties": {"payment_id": {"type": "string"}, "amount": {"type": "string"}, "status": {"type": "string", "enum": ["succeeded", "failed", "pending"]}, "created_at": {"type": "string"}, "method": {"type": "string", "example": "Visa ending 4242"}, "provider": {"type": "string", "enum": ["braintree", "manual"]}, "message": {"type": "string"}}}}}}, "PriceBook": {"type": "object", "required": ["services"], "properties": {"currency": {"type": "string", "example": "usd"}, "version": {"type": "string", "readOnly": true, "description": "When it was saved."}, "updated_by": {"type": "string", "readOnly": true}, "plans": {"type": "array", "description": "Billing plans. Only the ones agencies can choose are listed for agency admins.", "items": {"$ref": "#/components/schemas/BillingPlan"}}, "tiers": {"type": "array", "description": "Tiers an agency can pick per service.", "items": {"$ref": "#/components/schemas/Tier"}}, "services": {"type": "array", "items": {"type": "object", "required": ["service"], "properties": {"service": {"type": "string", "example": "attribution"}, "label": {"type": "string"}, "pricing": {"type": "string", "enum": ["agency", "client"], "description": "`agency`: base fee per agency with clients included (current prices). `client`: an old per-client price book."}, "base_fee": {"type": "string", "description": "Monthly base fee (per agency; per client on an old per-client book).", "example": "149"}, "included_clients": {"type": "string", "description": "Clients the base fee includes (core tier).", "example": "1"}, "client_fee": {"type": "string", "description": "Monthly fee for each client above those included (core tier).", "example": "69"}, "allowance": {"type": "string", "enum": ["client", "agency"], "description": "`client`: each billable client adds `included` to the pool. `agency`: one allowance per agency."}, "credit": {"type": "string", "description": "Money of usage included per agency each month (AI Services).", "example": "10"}, "client_cost": {"type": "string", "description": "What keeping a client on the service costs us a month (charged on cost-plus plans).", "example": "0.20"}, "free_cost_pct": {"type": "string", "description": "Old per-client books only: the free usage may cost us at most this percent of the base fee.", "example": "1"}, "free_units": {"type": "object", "readOnly": true, "additionalProperties": {"type": "integer"}}, "metrics": {"type": "array", "items": {"type": "object", "required": ["metric"], "properties": {"metric": {"type": "string", "example": "sessions"}, "label": {"type": "string"}, "unit": {"type": "string", "example": "session"}, "unit_cost": {"type": "string", "description": "What one unit costs us.", "example": "0.000004"}, "unit_price": {"type": "string", "description": "Price per unit above the allowance (core tier).", "example": "0.0002"}, "included": {"type": "string", "description": "Units each billable client adds to the pooled allowance.", "example": "100000"}, "free_share": {"type": "string", "description": "Share (0-1) of the free budget spent on this metric.", "example": "1"}}}}}}}, "trial": {"type": "object", "description": "The free trial self-serve signups get.", "properties": {"days": {"type": "integer", "example": 14}, "services": {"type": "array", "items": {"type": "string"}, "description": "Services turned on for the first client."}, "max_clients": {"type": "integer", "example": 1}, "limits": {"type": "object", "description": "{service: {metric: max}}. Connections and custom domains cap how many exist at once; tool calls and sessions cap the trial's total.", "additionalProperties": {"type": "object", "additionalProperties": {"type": "integer"}}}}}}}, "Name": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,63}$", "example": "main-site"}, "Error": {"type": "object", "required": ["error"], "properties": {"error": {"type": "string", "example": "forbidden", "description": "A stable code; see the Error response."}, "message": {"type": "string", "description": "What went wrong", "for people.": null}, "request_id": {"type": "string", "description": "Identifies the call if you contact support."}}}, "Client": {"type": "object", "properties": {"client": {"$ref": "#/components/schemas/ClientId"}, "name": {"type": "string"}, "status": {"type": "string", "enum": ["active", "disabled"]}, "agency_id": {"$ref": "#/components/schemas/AgencyId"}, "services": {"type": "array", "items": {"type": "string"}, "description": "Paid services turned on (base fee each)."}, "contact": {"$ref": "#/components/schemas/Contact"}}}, "Connection": {"type": "object", "properties": {"service": {"type": "string", "enum": ["ga4", "gtm", "gsc", "gbp", "merchant", "ads", "adsense", "datamanager", "pagespeed", "shopify", "meta_ads", "facebook", "instagram", "meta_business", "wordpress", "callrail", "microsoft_ads", "bing_webmaster", "supermetrics", "salesforce", "mntn", "ringex", "ringcx", "netsuite", "hubspot", "snapfulfil", "techship", "mailchimp", "gorgias", "klaviyo"]}, "name": {"type": "string"}, "default": {"type": "boolean"}, "allow_write": {"type": "boolean"}, "auth_type": {"type": "string"}, "description": {"type": "string"}, "settings": {"type": "object", "additionalProperties": {"type": "string"}}, "data_source": {"type": "string", "description": "The data source it uses (\"\" for connections made before data sources)"}, "key": {"type": "string", "description": "service/name"}}}, "DataSource": {"type": "object", "properties": {"name": {"type": "string"}, "type": {"type": "string", "enum": ["google", "shopify", "meta"]}, "label": {"type": "string"}, "description": {"type": "string"}, "auth_type": {"type": "string"}, "account": {"type": "string", "description": "The Google, Facebook, Microsoft or Salesforce account, Shopify store or site it signs in as (for Bing Webmaster Tools: the first site and how many more)"}, "signed_in": {"type": "boolean", "description": "False until someone signs in (e.g. a data source copied to a staging account without credentials)"}, "scopes": {"type": "array", "items": {"type": "string"}}, "allowed": {"type": "object", "description": "Which accounts and properties each service may reach (\"level:id\" items per service). Services not\nlisted may reach everything the sign-in can see.\n", "additionalProperties": {"type": "array", "items": {"type": "string"}}}, "created_at": {"type": "string"}, "updated_at": {"type": "string"}, "used_by": {"type": "array", "items": {"type": "string"}, "description": "Connections using it, as \"service/name\""}, "health": {"type": "string", "enum": ["", "ok", "expired", "failing", "unreachable"], "description": "Result of the last sign-in check: `ok`, `expired` (sign in again), `failing` (rejected for\nanother reason, e.g. the store is closed), `unreachable` (we couldn't check), or empty if not\nchecked yet.\n"}, "health_message": {"type": "string", "description": "What went wrong", "in plain words": null}, "checked_at": {"type": "string", "description": "When the last check ran"}, "failing_since": {"type": "string", "description": "When it stopped working (empty while it works)"}}}, "ActivityEntry": {"type": "object", "properties": {"at": {"type": "string", "description": "When (ISO 8601", "UTC)": null}, "actor": {"type": "string", "description": "Who made the change (their email)"}, "summary": {"type": "string", "description": "What happened, in words, e.g. \"turned off Google Ads\""}, "action": {"type": "string", "description": "Stable name of the change: the API operation (e.g. set_client_service) or \"tool:<gateway tool>\""}, "via": {"type": "string", "enum": ["page", "api", "gateway", "cli", "sign-in", "system"]}, "via_label": {"type": "string", "description": "Where it came from, in words, e.g. \"Admin API\""}, "kind": {"type": "string", "enum": ["change", "sign-in", "session", "deploy"], "description": "What it is: a change, a sign-in, a super admin log-in to an account, or a deploy"}, "kind_label": {"type": "string", "description": "The kind in words, e.g. \"Sign-in\""}, "username": {"type": "string", "description": "The person's login ID"}, "region": {"type": "string", "description": "The AWS Region that recorded it, e.g. \"us-east-1\""}, "app_label": {"type": "string", "description": "Sign-ins only: what the person signed in to, e.g. \"the admin page\""}, "credential": {"type": "string", "description": "The API token or API/MCP Gateway key used, e.g. \"API token ab12cd34\" (empty for the admin page)"}, "role": {"type": "string", "description": "The person's role at the time"}, "client": {"type": "string"}, "agency_id": {"type": "string"}, "target": {"type": "string", "description": "What was changed, e.g. \"ga4/main\""}, "details": {"type": "object", "additionalProperties": true, "description": "Short", "non-secret values sent with the change": null}, "entry_id": {"type": "string"}}}, "ActivityPage": {"type": "object", "properties": {"activity": {"type": "array", "items": {"$ref": "#/components/schemas/ActivityEntry"}}, "next_cursor": {"type": ["string", "null"], "description": "Pass as `cursor` to get the next page; null on the last page."}}}, "Alert": {"type": "object", "properties": {"client": {"type": "string"}, "client_name": {"type": "string"}, "source": {"type": "string", "description": "The data source's name"}, "label": {"type": "string"}, "type": {"type": "string", "enum": ["google", "shopify", "meta"]}, "account": {"type": "string"}, "severity": {"type": "string", "enum": ["error", "warning"]}, "status": {"type": "string", "enum": ["expired", "failing", "not_signed_in"]}, "message": {"type": "string"}, "since": {"type": "string"}}}, "GatewayKey": {"type": "object", "properties": {"client": {"type": "string"}, "key_id": {"type": "string"}, "label": {"type": "string"}, "created_at": {"type": "string"}, "username": {"type": "string"}, "email": {"type": "string"}}}, "ApiToken": {"type": "object", "properties": {"token_id": {"type": "string"}, "username": {"type": "string"}, "email": {"type": "string"}, "label": {"type": "string"}, "scope": {"type": "string", "enum": ["read", "write"]}, "created_at": {"type": "string"}, "expires_at": {"type": "string"}, "last_used_at": {"type": "string"}}}, "ConnectedApp": {"type": "object", "properties": {"session_id": {"type": "string"}, "client": {"type": "string", "description": "The client the app was connected for"}, "app_name": {"type": "string", "description": "The connected app's name"}, "app_id": {"type": "string", "description": "The connected app's own ID (its OAuth client ID)"}, "api": {"type": "string", "enum": ["gateway", "admin"], "description": "Which MCP endpoint it signed in to: the API/MCP Gateway's (in one client) or this API's"}, "access": {"type": "string", "enum": ["read", "write"], "description": "For this API's sign-ins: what the app may do, as chosen when signing in"}, "email": {"type": "string"}, "created_at": {"type": "string"}, "last_used_at": {"type": "string"}}}, "Person": {"type": "object", "additionalProperties": true, "properties": {"username": {"type": "string"}, "email": {"type": "string"}, "status": {"type": "string", "description": "For example CONFIRMED, or FORCE_CHANGE_PASSWORD while the invitation is open"}, "enabled": {"type": "boolean"}, "created_at": {"type": "string"}, "admin": {"type": "boolean"}, "manager": {"type": "boolean"}, "clients": {"type": "array", "items": {"type": "string"}}, "managed": {"type": "array", "items": {"type": "string"}}}}, "Grants": {"type": "object", "description": "Connection (\"service/name\") to access level.", "additionalProperties": {"type": "string", "enum": ["none", "read", "write"]}, "example": {"ga4/main-site": "read", "shopify/store": "write"}}, "ServiceAccess": {"type": "object", "description": "Admin service access: service name to none, read (view) or write (view and edit). Separate from connection grants.", "additionalProperties": {"type": "string", "enum": ["none", "read", "write"]}, "example": {"ga4": "read", "shopify": "write"}}, "MyAccess": {"type": "object", "properties": {"username": {"type": "string"}, "email": {"type": "string"}, "global_admin": {"type": "boolean"}, "clients": {"type": "array", "items": {"type": "object", "properties": {"client": {"type": "string"}, "name": {"type": "string"}, "role": {"type": "string", "enum": ["admin", "manager", "user"]}, "admin_services": {"$ref": "#/components/schemas/ServiceAccess"}, "connections": {"type": "array", "items": {"type": "object", "additionalProperties": true}}, "services": {"type": "array", "items": {"type": "string"}, "description": "Paid services turned on for the client (gateway, attribution, ai)."}, "paused": {"type": "boolean", "description": "True while the client's services are paused (overdue invoice)."}}}}, "mcp_url": {"type": "string", "description": "The API/MCP Gateway (for your data)", "not this API.": null}}}, "ProviderInput": {"type": "object", "description": "`kind` is set when adding. Entra ID (`microsoft`) takes directory_id, client_id and\nclient_secret. Google Workspace (`google`) and other OIDC providers (`oidc`) take issuer,\nclient_id and client_secret. SAML (`saml`) takes metadata_url or metadata_xml.\n", "properties": {"kind": {"type": "string", "enum": ["oidc", "microsoft", "google", "saml"]}, "label": {"type": "string", "maxLength": 60}, "auto_join": {"type": "boolean", "description": "Add people who sign in this way to the client automatically."}, "issuer": {"type": "string"}, "directory_id": {"type": "string"}, "client_id": {"type": "string"}, "client_secret": {"type": "string", "writeOnly": true}, "metadata_url": {"type": "string"}, "metadata_xml": {"type": "string"}, "email_attribute": {"type": "string"}}}}}}