Skip to content

Marketing and reporting

The client’s numbers, the advertising that moves them, the container that measures them, and the monthly document that explains them.

129 endpoints across 4 groups. A permission written module.resource.action:own is scoped: :own covers rows that are yours, :any covers everyone’s, and :any satisfies a check for :own.

EndpointPermission
GET /marketing/accountsmarketing.link.manage
GET /marketing/clientsmarketing.metrics.read
GET /marketing/companies/{company_id}/ai-searchmarketing.metrics.read
POST /marketing/companies/{company_id}/ai-search/brandmarketing.link.manage
POST /marketing/companies/{company_id}/ai-search/refreshmarketing.link.manage
PUT /marketing/companies/{company_id}/ai-search/settingsmarketing.link.manage
GET /marketing/companies/{company_id}/drilldownmarketing.metrics.read
GET /marketing/companies/{company_id}/leadsmarketing.metrics.read
GET /marketing/companies/{company_id}/leads/catalogmarketing.link.manage
GET /marketing/companies/{company_id}/metricsmarketing.metrics.read
GET /marketing/companies/{company_id}/settingsmarketing.metrics.read
PUT /marketing/companies/{company_id}/settingsmarketing.link.manage
GET /marketing/companies/{company_id}/websitesmarketing.link.manage
GET /marketing/linksmarketing.metrics.read
POST /marketing/linksmarketing.link.manage
DELETE /marketing/links/{link_id}marketing.link.manage
POST /marketing/links/{link_id}/ai-visibility/importmarketing.link.manage
POST /marketing/links/{link_id}/ai-visibility/rowsmarketing.link.manage
GET /marketing/overviewmarketing.overview.read
GET /marketing/settingsmarketing.link.manage
PUT /marketing/settingsmarketing.link.manage
GET /marketing/settings/seranking/checkmarketing.link.manage
GET /marketing/summarymarketing.metrics.read
Parameters and schemas per endpoint

Available Accounts — The accounts/properties/sites the caller’s connection can reach for source.

Permission: marketing.link.manage

Query parameters:

NameTypeMeaning
sourceMarketingSource · required
website_idstringRequired for a source whose credential is per website (rankmath); ignored by every other source.
refreshbooleanSkip the short account cache and ask the provider again. What a picker offers after somebody has just created the thing they came here to link. (default false)

Returns: 200 · AccountsResponse

Linked Clients — The clients that actually have a source linked, with which sources and how each is doing.

Permission: marketing.metrics.read

Query parameters:

NameTypeMeaning
limitinteger(default 200)

Returns: 200 · MarketingClientList

Section titled “GET /api/v1/marketing/companies/{company_id}/ai-search”

Company Ai Search — How visible this client’s brand is in AI answers (ChatGPT, Perplexity, Gemini, Google’s AI Overviews and AI Mode) last month, against the month before — SE Ranking’s AI Search overview (docs/SERANKING.md): brand presence, link presence, average position and AI opportunity traffic, plus the monthly series behind them.

Permission: marketing.metrics.read

Path parameters: company_id

Returns: 200 · AiSearchOverview

POST /api/v1/marketing/companies/{company_id}/ai-search/brand

Section titled “POST /api/v1/marketing/companies/{company_id}/ai-search/brand”

Lookup Company Ai Search Brand — The brand name(s) SE Ranking attributes to this client’s target (100 units). Brand presence counts that brand’s mentions, so a wrong attribution is a wrong number — look it up, and set the brand explicitly where SE Ranking’s answer is not the client’s trade name.

Permission: marketing.link.manage

Path parameters: company_id

Request body: BrandLookupRequest

Returns: 200 · BrandLookup

POST /api/v1/marketing/companies/{company_id}/ai-search/refresh

Section titled “POST /api/v1/marketing/companies/{company_id}/ai-search/refresh”

Refresh Company Ai Search — Ask SE Ranking again now, whatever is stored. Spends 800 units per configured engine choice — which is why it is the manage permission and its own verb, never a parameter on the read: after a changed key, a topped-up plan or a corrected brand.

Permission: marketing.link.manage

Path parameters: company_id

Returns: 200 · AiSearchOverview

PUT /api/v1/marketing/companies/{company_id}/ai-search/settings

Section titled “PUT /api/v1/marketing/companies/{company_id}/ai-search/settings”

Set Company Ai Search Settings — This client’s AI Search settings, as a diff over the house defaults in Instellingen → Marketing: on/off, engines (all is SE Ranking’s cross-engine aggregate), the country database, the scope, the target and the brand. Posted whole — a field left null follows the house default. Saving asks SE Ranking nothing; the next read does.

Permission: marketing.link.manage

Path parameters: company_id

Request body: AiSearchCompanySettingsWrite

Returns: 200 · AiSearchOverview

GET /api/v1/marketing/companies/{company_id}/drilldown

Section titled “GET /api/v1/marketing/companies/{company_id}/drilldown”

Drilldown — A live tier-2 drill-down (top pages/queries/campaigns), Redis-cached ~1h.

Permission: marketing.metrics.read

Path parameters: company_id

Query parameters:

NameTypeMeaning
link_idstring · required
kindstring · required
range_daysinteger(default 30)
periodstringThe span to report on: a trailing window (30d, 90d, 365d), a preset (month, last_month, quarter, last_quarter) or a named calendar period (2026-07, 2026-Q3). Wins over range_days; an unknown value falls back to 30d.

Returns: 200 · DrilldownResponse

GET /api/v1/marketing/companies/{company_id}/leads

Section titled “GET /api/v1/marketing/companies/{company_id}/leads”

Company Leads — The leads dashboard for one client: form, call and e-mail conversions from GA4 and the ad spend behind them from Google Ads, read through the client’s measurement profile (docs/MARKETING.md). configured is false for a client without a profile.

Permission: marketing.metrics.read

Path parameters: company_id

Query parameters:

NameTypeMeaning
periodstringThe span to report on: a trailing window (30d), a preset (month, last_month), a named period (2026-07, 2026-Q3) or a free span (2026-08-29..2026-09-03). Defaults to the last 30 complete days; a span is clamped to end yesterday.
fstring[]Dimension filters, repeatable, as dimension:value on the profile’s dimension keys (service, form_type, language, …). Several values of one dimension are OR-ed, different dimensions AND-ed. An unknown dimension is refused, never ignored. (default “)

Returns: 200 · LeadsDashboard

GET /api/v1/marketing/companies/{company_id}/leads/catalog

Section titled “GET /api/v1/marketing/companies/{company_id}/leads/catalog”

Company Leads Catalog — What the client’s GA4 property and Ads account carry — event names, custom dimensions, key events, conversion actions — so a measurement profile is picked from what exists.

Permission: marketing.link.manage

Path parameters: company_id

Returns: 200 · LeadsCatalog

GET /api/v1/marketing/companies/{company_id}/metrics

Section titled “GET /api/v1/marketing/companies/{company_id}/metrics”

Company Metrics

Permission: marketing.metrics.read

Path parameters: company_id

Query parameters:

NameTypeMeaning
range_daysinteger(default 30)
periodstringThe span to report on: a trailing window (30d, 90d, 365d), a preset (month, last_month, quarter, last_quarter) or a named calendar period (2026-07, 2026-Q3). Wins over range_days; an unknown value falls back to 30d.

Returns: 200 · CompanyMarketing

GET /api/v1/marketing/companies/{company_id}/settings

Section titled “GET /api/v1/marketing/companies/{company_id}/settings”

Get Company Settings — This client’s marketing preferences, and what they resolve to.

Permission: marketing.metrics.read

Path parameters: company_id

Returns: 200 · CompanySettingsRead

PUT /api/v1/marketing/companies/{company_id}/settings

Section titled “PUT /api/v1/marketing/companies/{company_id}/settings”

Set Company Settings — Per-client marketing preferences: the curated tab layout (#192), the comparison this client’s dashboard measures against (#312) and the legacy key-events toggle (#134).

Permission: marketing.link.manage

Path parameters: company_id

Request body: CompanySettingsUpdate

Returns: 200 · CompanySettingsRead

GET /api/v1/marketing/companies/{company_id}/websites

Section titled “GET /api/v1/marketing/companies/{company_id}/websites”

Company Websites — This client’s websites — which one a new link attaches to (#399).

Permission: marketing.link.manage

Path parameters: company_id

Returns: 200 · WebsiteRef[]

List Links

Permission: marketing.metrics.read

Query parameters:

NameTypeMeaning
company_idstring · required

Returns: 200 · app__modules__marketing__schemas__LinkRead[]

Create Link

Permission: marketing.link.manage

Request body: app__modules__marketing__schemas__LinkCreate

Returns: 201 · app__modules__marketing__schemas__LinkRead

Unlink

Permission: marketing.link.manage

Path parameters: link_id

Returns: 204

POST /api/v1/marketing/links/{link_id}/ai-visibility/import

Section titled “POST /api/v1/marketing/links/{link_id}/ai-visibility/import”

Import Ai Visibility — Upload the export of Search Console’s Generative AI performance report — the CSV of its Dates table or the zip the console’s export button produces — for one Search Console link. Google returns these figures through no API, so this is how the site’s impressions in AI Overviews and AI Mode reach the dashboard tile and the report section. Days in the file overwrite the same days; days it does not name are left alone. Multipart; an agent sends the same rows as JSON to /ai-visibility/rows.

Permission: marketing.link.manage

Path parameters: link_id

Request body: Body_import_ai_visibility_api_v1_marketing_links__link_id__ai_visibility_import_post

Returns: 200 · AiVisibilityImportResult

POST /api/v1/marketing/links/{link_id}/ai-visibility/rows

Section titled “POST /api/v1/marketing/links/{link_id}/ai-visibility/rows”

Import Ai Visibility Rows — The JSON twin of the multipart import: the Dates table of Search Console’s Generative AI performance report as [{day, impressions}], for one Search Console link. Same rules, same result — a day named here overwrites that day, a day not named is left alone.

Permission: marketing.link.manage

Path parameters: link_id

Request body: AiVisibilityRows

Returns: 200 · AiVisibilityImportResult

Overview — The morning-coffee grid: one row per linked client, from stored data, server-sorted.

Permission: marketing.overview.read

Query parameters:

NameTypeMeaning
range_daysinteger(default 30)
sortstringcompany_name | sessions | clicks | position | cost | conversions (- = desc)
periodstringThe span to report on: a trailing window (30d, 90d, 365d), a preset (month, last_month, quarter, last_quarter) or a named calendar period (2026-07, 2026-Q3). Wins over range_days; an unknown value falls back to 30d.

Returns: 200 · OverviewResponse

Get Settings — The org’s marketing settings — reports whether an Ads developer token is configured; the token itself is write-only and never returned (the Google client-secret pattern).

Permission: marketing.link.manage

Returns: 200 · MarketingSettingsRead

Save Settings — Store the encrypted Google Ads developer token (an empty value keeps the stored one).

Permission: marketing.link.manage

Request body: MarketingSettingsWrite

Returns: 200 · MarketingSettingsRead

GET /api/v1/marketing/settings/seranking/check

Section titled “GET /api/v1/marketing/settings/seranking/check”

Check Seranking Key — Which of SE Ranking’s two APIs the stored key reaches — the project API (rankings) and the Data API (AI Search) — with the Data API plan’s units left, and what the AI Search overview costs per month at the current settings. Free: neither probe is a charged call.

Permission: marketing.link.manage

Returns: 200 · SeRankingCheck

Summary — The dashboard widget’s compact digest: top linked clients by their headline KPI, from stored data. Horizon-scoped like the per-company metrics read it summarizes — never wider than what the caller could fetch client-by-client.

Permission: marketing.metrics.read

Query parameters:

NameTypeMeaning
range_daysinteger(default 30)
limitinteger(default 5)
periodstringThe span to report on: a trailing window (30d, 90d, 365d), a preset (month, last_month, quarter, last_quarter) or a named calendar period (2026-07, 2026-Q3). Wins over range_days; an unknown value falls back to 30d.

Returns: 200 · MarketingSummary

EndpointPermission
GET /google-ads/accountsgoogle_ads.account.read
POST /google-ads/accountsgoogle_ads.settings.manage
GET /google-ads/accounts/{account_id}google_ads.account.read
PATCH /google-ads/accounts/{account_id}google_ads.settings.manage
DELETE /google-ads/accounts/{account_id}google_ads.settings.manage
GET /google-ads/accounts/{account_id}/ad-groupsgoogle_ads.account.read
POST /google-ads/accounts/{account_id}/ad-groupsgoogle_ads.campaign.write
PATCH /google-ads/accounts/{account_id}/ad-groups/{ad_group_id}google_ads.campaign.write
DELETE /google-ads/accounts/{account_id}/ad-groups/{ad_group_id}google_ads.campaign.write
DELETE /google-ads/accounts/{account_id}/ad-groups/{ad_group_id}/ads/{ad_id}google_ads.campaign.write
GET /google-ads/accounts/{account_id}/adsgoogle_ads.account.read
POST /google-ads/accounts/{account_id}/adsgoogle_ads.campaign.write
PATCH /google-ads/accounts/{account_id}/adsgoogle_ads.campaign.write
POST /google-ads/accounts/{account_id}/budgetsgoogle_ads.budget.write
PATCH /google-ads/accounts/{account_id}/budgets/{budget_id}google_ads.budget.write
DELETE /google-ads/accounts/{account_id}/budgets/{budget_id}google_ads.budget.write
GET /google-ads/accounts/{account_id}/campaignsgoogle_ads.account.read
POST /google-ads/accounts/{account_id}/campaignsgoogle_ads.campaign.write
PATCH /google-ads/accounts/{account_id}/campaigns/{campaign_id}google_ads.campaign.write
DELETE /google-ads/accounts/{account_id}/campaigns/{campaign_id}google_ads.campaign.write
GET /google-ads/accounts/{account_id}/changesgoogle_ads.account.read
GET /google-ads/accounts/{account_id}/conversionsgoogle_ads.account.read
GET /google-ads/accounts/{account_id}/decisionsgoogle_ads.account.read
POST /google-ads/accounts/{account_id}/decisionsgoogle_ads.policy.manage
DELETE /google-ads/accounts/{account_id}/decisions/{decision_id}google_ads.policy.manage
GET /google-ads/accounts/{account_id}/devicesgoogle_ads.account.read
GET /google-ads/accounts/{account_id}/geogoogle_ads.account.read
POST /google-ads/accounts/{account_id}/keyword-ideasgoogle_ads.account.read
GET /google-ads/accounts/{account_id}/keywordsgoogle_ads.account.read
POST /google-ads/accounts/{account_id}/keywordsgoogle_ads.keyword.write
PATCH /google-ads/accounts/{account_id}/keywordsgoogle_ads.keyword.write
POST /google-ads/accounts/{account_id}/keywords/removegoogle_ads.keyword.write
POST /google-ads/accounts/{account_id}/negative-listsgoogle_ads.negative.write
DELETE /google-ads/accounts/{account_id}/negative-lists/{shared_set_id}google_ads.negative.write
GET /google-ads/accounts/{account_id}/negativesgoogle_ads.account.read
POST /google-ads/accounts/{account_id}/negativesgoogle_ads.negative.write
POST /google-ads/accounts/{account_id}/negatives/removegoogle_ads.negative.write
GET /google-ads/accounts/{account_id}/policygoogle_ads.policy.manage
PUT /google-ads/accounts/{account_id}/policygoogle_ads.policy.manage
DELETE /google-ads/accounts/{account_id}/policygoogle_ads.policy.manage
POST /google-ads/accounts/{account_id}/querygoogle_ads.query.run
GET /google-ads/accounts/{account_id}/recommendationsgoogle_ads.account.read
GET /google-ads/accounts/{account_id}/search-termsgoogle_ads.account.read
GET /google-ads/accounts/{account_id}/snapshotgoogle_ads.account.read
GET /google-ads/accounts/{account_id}/trendgoogle_ads.account.read
POST /google-ads/accounts/{account_id}/verifygoogle_ads.settings.manage
GET /google-ads/accounts/availablegoogle_ads.settings.manage
GET /google-ads/policygoogle_ads.policy.manage
PUT /google-ads/policygoogle_ads.policy.manage
DELETE /google-ads/policygoogle_ads.policy.manage
GET /google-ads/settingsgoogle_ads.settings.manage
PUT /google-ads/settingsgoogle_ads.settings.manage
Parameters and schemas per endpoint

List Google Ads Accounts — Every linked Google Ads account this caller may see — start here.

Permission: google_ads.account.read

Query parameters:

NameTypeMeaning
company_idstring
active_onlyboolean(default false)

Returns: 200 · GoogleAdsAccountRead[]

Link Google Ads Account — Link an Ads account to a client.

Permission: google_ads.settings.manage

Request body: GoogleAdsAccountCreate

Returns: 201 · GoogleAdsAccountRead

GET /api/v1/google-ads/accounts/{account_id}

Section titled “GET /api/v1/google-ads/accounts/{account_id}”

Get Google Ads Account

Permission: google_ads.account.read

Path parameters: account_id

Returns: 200 · GoogleAdsAccountRead

PATCH /api/v1/google-ads/accounts/{account_id}

Section titled “PATCH /api/v1/google-ads/accounts/{account_id}”

Update Google Ads Account

Permission: google_ads.settings.manage

Path parameters: account_id

Request body: GoogleAdsAccountUpdate

Returns: 200 · GoogleAdsAccountRead

DELETE /api/v1/google-ads/accounts/{account_id}

Section titled “DELETE /api/v1/google-ads/accounts/{account_id}”

Unlink Google Ads Account — Deactivate the link. The row survives: history hangs off it, and a re-link must find the same account rather than collide with its own unique constraint.

Permission: google_ads.settings.manage

Path parameters: account_id

Returns: 204

GET /api/v1/google-ads/accounts/{account_id}/ad-groups

Section titled “GET /api/v1/google-ads/accounts/{account_id}/ad-groups”

Google Ads Ad Groups — Ad-group performance, most expensive first. One level below campaigns.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
campaignsstring[]
include_removedboolean(default false)
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
statusstringOnly rows with this Google status: ENABLED, PAUSED or REMOVED. REMOVED implies include_removed, because a filter that always answers nothing is not a filter.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

POST /api/v1/google-ads/accounts/{account_id}/ad-groups

Section titled “POST /api/v1/google-ads/accounts/{account_id}/ad-groups”

Create Google Ads Ad Group — Create an ad group inside an existing campaign. Paused, like everything created here.

Permission: google_ads.campaign.write

Path parameters: account_id

Request body: GoogleAdsAdGroupCreate

Returns: 201 · GoogleAdsMutationRead

PATCH /api/v1/google-ads/accounts/{account_id}/ad-groups/{ad_group_id}

Section titled “PATCH /api/v1/google-ads/accounts/{account_id}/ad-groups/{ad_group_id}”

Update Google Ads Ad Group — Pause, resume, remove, rename or re-bid an ad group.

Permission: google_ads.campaign.write

Path parameters: account_id, ad_group_id

Request body: GoogleAdsAdGroupUpdate

Returns: 200 · GoogleAdsMutationRead

DELETE /api/v1/google-ads/accounts/{account_id}/ad-groups/{ad_group_id}

Section titled “DELETE /api/v1/google-ads/accounts/{account_id}/ad-groups/{ad_group_id}”

Remove Google Ads Ad Group — Delete an ad group, with the keywords and ads inside it. Permanent at Google.

Permission: google_ads.campaign.write

Path parameters: account_id, ad_group_id

Query parameters:

NameTypeMeaning
validate_onlybooleanCheck and change nothing. (default false)

Returns: 200 · GoogleAdsMutationRead

DELETE /api/v1/google-ads/accounts/{account_id}/ad-groups/{ad_group_id}/ads/{ad_id}

Section titled “DELETE /api/v1/google-ads/accounts/{account_id}/ad-groups/{ad_group_id}/ads/{ad_id}”

Remove Google Ads Ad — Delete one ad. Permanent at Google — a replacement is a new ad with its own history.

Permission: google_ads.campaign.write

Path parameters: account_id, ad_group_id, ad_id

Query parameters:

NameTypeMeaning
validate_onlybooleanCheck and change nothing. (default false)

Returns: 200 · GoogleAdsMutationRead

GET /api/v1/google-ads/accounts/{account_id}/ads

Section titled “GET /api/v1/google-ads/accounts/{account_id}/ads”

Google Ads Ads — Ads with their ad strength and policy approval status, most expensive first.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
campaignsstring[]
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
statusstringOnly rows with this Google status: ENABLED, PAUSED or REMOVED. REMOVED implies include_removed, because a filter that always answers nothing is not a filter.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

POST /api/v1/google-ads/accounts/{account_id}/ads

Section titled “POST /api/v1/google-ads/accounts/{account_id}/ads”

Create Google Ads Ad — Create a responsive search ad. It is paused until somebody reads it and enables it.

Permission: google_ads.campaign.write

Path parameters: account_id

Request body: GoogleAdsAdCreate

Returns: 201 · GoogleAdsMutationRead

PATCH /api/v1/google-ads/accounts/{account_id}/ads

Section titled “PATCH /api/v1/google-ads/accounts/{account_id}/ads”

Update Google Ads Ad — Pause or resume one ad. status is ENABLED or PAUSED; to delete it, use DELETE.

Permission: google_ads.campaign.write

Path parameters: account_id

Request body: GoogleAdsAdUpdate

Returns: 200 · GoogleAdsMutationRead

POST /api/v1/google-ads/accounts/{account_id}/budgets

Section titled “POST /api/v1/google-ads/accounts/{account_id}/budgets”

Create Google Ads Budget — Create a daily budget, in the account’s own currency.

Permission: google_ads.budget.write

Path parameters: account_id

Request body: GoogleAdsBudgetCreate

Returns: 201 · GoogleAdsMutationRead

PATCH /api/v1/google-ads/accounts/{account_id}/budgets/{budget_id}

Section titled “PATCH /api/v1/google-ads/accounts/{account_id}/budgets/{budget_id}”

Update Google Ads Budget — Change a daily budget — the one write where being wrong has already cost money.

Permission: google_ads.budget.write

Path parameters: account_id, budget_id

Request body: GoogleAdsBudgetUpdate

Returns: 200 · GoogleAdsMutationRead

DELETE /api/v1/google-ads/accounts/{account_id}/budgets/{budget_id}

Section titled “DELETE /api/v1/google-ads/accounts/{account_id}/budgets/{budget_id}”

Remove Google Ads Budget — Delete a daily budget.

Permission: google_ads.budget.write

Path parameters: account_id, budget_id

Query parameters:

NameTypeMeaning
validate_onlybooleanCheck and change nothing. (default false)

Returns: 200 · GoogleAdsMutationRead

GET /api/v1/google-ads/accounts/{account_id}/campaigns

Section titled “GET /api/v1/google-ads/accounts/{account_id}/campaigns”

Google Ads Campaigns — Campaign performance and settings, most expensive first.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
campaignsstring[]Filter by campaign name, case-insensitive substring match.
include_removedbooleanInclude removed campaigns. Off by default: a list where a third of the rows cannot be acted on is a worse answer to ‘what are we running’. Turn it on to ask what was spent on things since removed. (default false)
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
statusstringOnly rows with this Google status: ENABLED, PAUSED or REMOVED. REMOVED implies include_removed, because a filter that always answers nothing is not a filter.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

POST /api/v1/google-ads/accounts/{account_id}/campaigns

Section titled “POST /api/v1/google-ads/accounts/{account_id}/campaigns”

Create Google Ads Campaign — Create a campaign. It is always paused and spends nothing until somebody enables it.

Permission: google_ads.campaign.write

Path parameters: account_id

Request body: GoogleAdsCampaignCreate

Returns: 201 · GoogleAdsMutationRead

PATCH /api/v1/google-ads/accounts/{account_id}/campaigns/{campaign_id}

Section titled “PATCH /api/v1/google-ads/accounts/{account_id}/campaigns/{campaign_id}”

Update Google Ads Campaign — Pause, resume, remove or rename a campaign. status is ENABLED, PAUSED or REMOVED.

Permission: google_ads.campaign.write

Path parameters: account_id, campaign_id

Request body: GoogleAdsCampaignUpdate

Returns: 200 · GoogleAdsMutationRead

DELETE /api/v1/google-ads/accounts/{account_id}/campaigns/{campaign_id}

Section titled “DELETE /api/v1/google-ads/accounts/{account_id}/campaigns/{campaign_id}”

Remove Google Ads Campaign — Delete a campaign. Permanent at Google — it can be recreated, never restored.

Permission: google_ads.campaign.write

Path parameters: account_id, campaign_id

Query parameters:

NameTypeMeaning
validate_onlybooleanCheck and change nothing. (default false)

Returns: 200 · GoogleAdsMutationRead

GET /api/v1/google-ads/accounts/{account_id}/changes

Section titled “GET /api/v1/google-ads/accounts/{account_id}/changes”

Google Ads Changes — What was changed in the account, with each field’s old and new value.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

GET /api/v1/google-ads/accounts/{account_id}/conversions

Section titled “GET /api/v1/google-ads/accounts/{account_id}/conversions”

Google Ads Conversion Health — What this account optimises toward, and what each conversion action actually recorded.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

GET /api/v1/google-ads/accounts/{account_id}/decisions

Section titled “GET /api/v1/google-ads/accounts/{account_id}/decisions”

List Google Ads Decisions — What has already been decided about this account — check this before proposing a change.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
subject_typestring
decisionstring
include_withdrawnboolean(default false)
limitinteger(default 50)
offsetinteger(default 0)
countbooleanSet false to skip the total. (default true)

Returns: 200 · GoogleAdsDecisionPage

POST /api/v1/google-ads/accounts/{account_id}/decisions

Section titled “POST /api/v1/google-ads/accounts/{account_id}/decisions”

Record Google Ads Decision — Write down a judgement that changes nothing in Google.

Permission: google_ads.policy.manage

Path parameters: account_id

Request body: GoogleAdsDecisionCreate

Returns: 201 · GoogleAdsDecisionRead

DELETE /api/v1/google-ads/accounts/{account_id}/decisions/{decision_id}

Section titled “DELETE /api/v1/google-ads/accounts/{account_id}/decisions/{decision_id}”

Withdraw Google Ads Decision — Unsay a decision. The row survives, marked withdrawn and by whom.

Permission: google_ads.policy.manage

Path parameters: account_id, decision_id

Returns: 200 · GoogleAdsDecisionRead

GET /api/v1/google-ads/accounts/{account_id}/devices

Section titled “GET /api/v1/google-ads/accounts/{account_id}/devices”

Google Ads Devices — Performance per device, per campaign, plus an account-wide rollup in extra.device_totals.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
campaignsstring[]
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

GET /api/v1/google-ads/accounts/{account_id}/geo

Section titled “GET /api/v1/google-ads/accounts/{account_id}/geo”

Google Ads Geo — Where the people who saw the ads physically were — not where the campaign targets.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
campaignsstring[]
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

POST /api/v1/google-ads/accounts/{account_id}/keyword-ideas

Section titled “POST /api/v1/google-ads/accounts/{account_id}/keyword-ideas”

Google Ads Keyword Ideas — Keyword ideas with search volume and competition, from seed terms or a landing page.

Permission: google_ads.account.read

Path parameters: account_id

Request body: GoogleAdsKeywordIdeaRequest

Returns: 200 · GoogleAdsReport

GET /api/v1/google-ads/accounts/{account_id}/keywords

Section titled “GET /api/v1/google-ads/accounts/{account_id}/keywords”

Google Ads Keywords — Positive keywords with match type, bid and Quality Score, most expensive first.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
campaignsstring[]
include_removedboolean(default false)
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
statusstringOnly rows with this Google status: ENABLED, PAUSED or REMOVED. REMOVED implies include_removed, because a filter that always answers nothing is not a filter.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

POST /api/v1/google-ads/accounts/{account_id}/keywords

Section titled “POST /api/v1/google-ads/accounts/{account_id}/keywords”

Add Google Ads Keywords — Add positive keywords to one ad group. match_type is EXACT, PHRASE or BROAD.

Permission: google_ads.keyword.write

Path parameters: account_id

Request body: GoogleAdsKeywordsAdd

Returns: 201 · GoogleAdsMutationRead

PATCH /api/v1/google-ads/accounts/{account_id}/keywords

Section titled “PATCH /api/v1/google-ads/accounts/{account_id}/keywords”

Update Google Ads Keyword — Pause, resume, remove or re-bid one keyword.

Permission: google_ads.keyword.write

Path parameters: account_id

Request body: GoogleAdsKeywordUpdate

Returns: 200 · GoogleAdsMutationRead

POST /api/v1/google-ads/accounts/{account_id}/keywords/remove

Section titled “POST /api/v1/google-ads/accounts/{account_id}/keywords/remove”

Remove Google Ads Keywords — Remove keywords from an ad group, by criterion id.

Permission: google_ads.keyword.write

Path parameters: account_id

Request body: GoogleAdsKeywordsRemove

Returns: 200 · GoogleAdsMutationRead

POST /api/v1/google-ads/accounts/{account_id}/negative-lists

Section titled “POST /api/v1/google-ads/accounts/{account_id}/negative-lists”

Create Google Ads Negative List — Create a shared negative-keyword list and optionally attach it to campaigns.

Permission: google_ads.negative.write

Path parameters: account_id

Request body: GoogleAdsNegativeListCreate

Returns: 201 · GoogleAdsMutationRead

DELETE /api/v1/google-ads/accounts/{account_id}/negative-lists/{shared_set_id}

Section titled “DELETE /api/v1/google-ads/accounts/{account_id}/negative-lists/{shared_set_id}”

Remove Google Ads Negative List — Delete a shared negative-keyword list.

Permission: google_ads.negative.write

Path parameters: account_id, shared_set_id

Query parameters:

NameTypeMeaning
validate_onlybooleanCheck and change nothing. (default false)

Returns: 200 · GoogleAdsMutationRead

GET /api/v1/google-ads/accounts/{account_id}/negatives

Section titled “GET /api/v1/google-ads/accounts/{account_id}/negatives”

Google Ads Negatives — Every negative keyword: ad-group level, campaign level, and shared negative lists.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

POST /api/v1/google-ads/accounts/{account_id}/negatives

Section titled “POST /api/v1/google-ads/accounts/{account_id}/negatives”

Add Google Ads Negatives — Exclude search terms — and record the ones you deliberately did not exclude.

Permission: google_ads.negative.write

Path parameters: account_id

Request body: GoogleAdsNegativesAdd

Returns: 201 · GoogleAdsMutationRead

POST /api/v1/google-ads/accounts/{account_id}/negatives/remove

Section titled “POST /api/v1/google-ads/accounts/{account_id}/negatives/remove”

Remove Google Ads Negatives — Take exclusions back off, by criterion id, at the level they were added.

Permission: google_ads.negative.write

Path parameters: account_id

Request body: GoogleAdsNegativesRemove

Returns: 200 · GoogleAdsMutationRead

GET /api/v1/google-ads/accounts/{account_id}/policy

Section titled “GET /api/v1/google-ads/accounts/{account_id}/policy”

Get Google Ads Policy — The rules that bind writes to this account — read this before proposing anything.

Permission: google_ads.policy.manage

Path parameters: account_id

Returns: 200 · GoogleAdsPolicyRead

PUT /api/v1/google-ads/accounts/{account_id}/policy

Section titled “PUT /api/v1/google-ads/accounts/{account_id}/policy”

Save Google Ads Policy — Set this account’s own rules.

Permission: google_ads.policy.manage

Path parameters: account_id

Request body: GoogleAdsPolicyWrite

Returns: 200 · GoogleAdsPolicyRead

DELETE /api/v1/google-ads/accounts/{account_id}/policy

Section titled “DELETE /api/v1/google-ads/accounts/{account_id}/policy”

Clear Google Ads Policy — Drop this account’s own rules, so it inherits the agency’s house policy again.

Permission: google_ads.policy.manage

Path parameters: account_id

Returns: 200 · GoogleAdsPolicyRead

POST /api/v1/google-ads/accounts/{account_id}/query

Section titled “POST /api/v1/google-ads/accounts/{account_id}/query”

Google Ads Query — Run a GAQL query against this account — the escape hatch for questions the other tools do not answer.

Permission: google_ads.query.run

Path parameters: account_id

Request body: GoogleAdsQueryRequest

Returns: 200 · GoogleAdsQueryRead

GET /api/v1/google-ads/accounts/{account_id}/recommendations

Section titled “GET /api/v1/google-ads/accounts/{account_id}/recommendations”

Google Ads Recommendations — Google’s own suggestions for this account, with the impact it projects for each.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

GET /api/v1/google-ads/accounts/{account_id}/search-terms

Section titled “GET /api/v1/google-ads/accounts/{account_id}/search-terms”

Google Ads Search Terms — What people actually typed, most expensive first.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
campaignsstring[]
min_costnumberOnly terms that cost at least this, in account currency.
min_clicksinteger
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.
qstringFree text, matched case-insensitively against the row’s own readable fields — the campaign or ad-group name, the keyword, the search term, the place. Applied to the whole list before the page is taken, so page 2 of a search is page 2 of the search.
limitintegerHow many rows this page holds. Omit for the rest of the list, which is what a caller with no pager means. Never more than the read’s own ceiling.
offsetintegerWhere the page starts. total_rows is what it runs to. (default 0)

Returns: 200 · GoogleAdsReport

GET /api/v1/google-ads/accounts/{account_id}/snapshot

Section titled “GET /api/v1/google-ads/accounts/{account_id}/snapshot”

Google Ads Snapshot — Account totals plus every campaign for the period — start an analysis here.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.

Returns: 200 · GoogleAdsSnapshotRead

GET /api/v1/google-ads/accounts/{account_id}/trend

Section titled “GET /api/v1/google-ads/accounts/{account_id}/trend”

Google Ads Trend — A period against its comparison, with the change per metric already computed.

Permission: google_ads.account.read

Path parameters: account_id

Query parameters:

NameTypeMeaning
comparestringWhat to compare against: ‘year’ (the same period a year earlier, the default) or ‘previous’ (the period immediately before).
periodstringA named span: 30d, 90d, month, last_month, quarter, last_quarter, 2026-07, 2026-Q3. Resolved in the account’s own timezone and always ending yesterday. Ignored when date_from and date_to are both given.
date_fromstringYYYY-MM-DD, inclusive.
date_tostringYYYY-MM-DD, inclusive.

Returns: 200 · GoogleAdsTrendRead

POST /api/v1/google-ads/accounts/{account_id}/verify

Section titled “POST /api/v1/google-ads/accounts/{account_id}/verify”

Verify Google Ads Account — Ask Google what it says about this account, and record the answer either way.

Permission: google_ads.settings.manage

Path parameters: account_id

Returns: 200 · GoogleAdsAccountRead

List Available Google Ads Accounts — Accounts the caller’s own Google grant can reach, manager hierarchies expanded.

Permission: google_ads.settings.manage

Returns: 200 · GoogleAdsPickerRead

Get Google Ads House Policy — The agency’s own standing Ads rules, applied to every account that does not override them.

Permission: google_ads.policy.manage

Returns: 200 · GoogleAdsPolicyRead

Save Google Ads House Policy — Set the agency’s standing rules. Fields you leave out are not touched.

Permission: google_ads.policy.manage

Request body: GoogleAdsPolicyWrite

Returns: 200 · GoogleAdsPolicyRead

Clear Google Ads House Policy — Drop the agency’s house rules, leaving only the built-in ceiling every account gets.

Permission: google_ads.policy.manage

Returns: 200 · GoogleAdsPolicyRead

Get Google Ads Settings — The org’s Ads configuration. The developer token is never part of the response.

Permission: google_ads.settings.manage

Returns: 200 · GoogleAdsSettingsRead

Save Google Ads Settings

Permission: google_ads.settings.manage

Request body: GoogleAdsSettingsWrite

Returns: 200 · GoogleAdsSettingsRead

EndpointPermission
GET /gtm/containersgoogle_tag_manager.container.read
POST /gtm/containersgoogle_tag_manager.settings.manage
GET /gtm/containers/{container_id}google_tag_manager.container.read
PATCH /gtm/containers/{container_id}google_tag_manager.settings.manage
DELETE /gtm/containers/{container_id}google_tag_manager.settings.manage
GET /gtm/containers/{container_id}/conversionsgoogle_tag_manager.container.read
POST /gtm/containers/{container_id}/conversionsgoogle_tag_manager.tag.write
GET /gtm/containers/{container_id}/snippetgoogle_tag_manager.container.read
GET /gtm/containers/{container_id}/statusgoogle_tag_manager.container.read
GET /gtm/containers/{container_id}/tagsgoogle_tag_manager.container.read
POST /gtm/containers/{container_id}/tagsgoogle_tag_manager.tag.write
PATCH /gtm/containers/{container_id}/tags/{tag_id}google_tag_manager.tag.write
DELETE /gtm/containers/{container_id}/tags/{tag_id}google_tag_manager.tag.write
GET /gtm/containers/{container_id}/triggersgoogle_tag_manager.container.read
POST /gtm/containers/{container_id}/triggersgoogle_tag_manager.tag.write
DELETE /gtm/containers/{container_id}/triggers/{trigger_id}google_tag_manager.tag.write
GET /gtm/containers/{container_id}/variablesgoogle_tag_manager.container.read
POST /gtm/containers/{container_id}/variablesgoogle_tag_manager.tag.write
DELETE /gtm/containers/{container_id}/variables/{variable_id}google_tag_manager.tag.write
POST /gtm/containers/{container_id}/verifygoogle_tag_manager.settings.manage
GET /gtm/containers/{container_id}/versionsgoogle_tag_manager.container.read
POST /gtm/containers/{container_id}/versionsgoogle_tag_manager.tag.write
POST /gtm/containers/{container_id}/versions/{version_id}/publishgoogle_tag_manager.version.publish
GET /gtm/containers/{container_id}/workspacegoogle_tag_manager.container.read
GET /gtm/containers/{container_id}/workspacesgoogle_tag_manager.container.read
GET /gtm/containers/availablegoogle_tag_manager.settings.manage
GET /gtm/settingsgoogle_tag_manager.settings.manage
PUT /gtm/settingsgoogle_tag_manager.settings.manage
Parameters and schemas per endpoint

List Gtm Containers — Every linked Tag Manager container this caller may see — start here.

Permission: google_tag_manager.container.read

Query parameters:

NameTypeMeaning
company_idstring
active_onlyboolean(default false)

Returns: 200 · GtmContainerRead[]

Link Gtm Container — Attach a container to this workspace, and say whose it is.

Permission: google_tag_manager.settings.manage

Request body: GtmContainerCreate

Returns: 201 · GtmContainerRead

Get Gtm Container

Permission: google_tag_manager.container.read

Path parameters: container_id

Returns: 200 · GtmContainerRead

PATCH /api/v1/gtm/containers/{container_id}

Section titled “PATCH /api/v1/gtm/containers/{container_id}”

Update Gtm Container

Permission: google_tag_manager.settings.manage

Path parameters: container_id

Request body: GtmContainerUpdate

Returns: 200 · GtmContainerRead

DELETE /api/v1/gtm/containers/{container_id}

Section titled “DELETE /api/v1/gtm/containers/{container_id}”

Unlink Gtm Container — Forget the container here. Nothing is removed from Tag Manager — an agency that stops working for a client does not thereby delete the tracking off their website.

Permission: google_tag_manager.settings.manage

Path parameters: container_id

Returns: 204

GET /api/v1/gtm/containers/{container_id}/conversions

Section titled “GET /api/v1/gtm/containers/{container_id}/conversions”

List Gtm Conversions — The conversions schakl set up in this container, and whether each is live yet.

Permission: google_tag_manager.container.read

Path parameters: container_id

Returns: 200 · GtmConversionRead[]

POST /api/v1/gtm/containers/{container_id}/conversions

Section titled “POST /api/v1/gtm/containers/{container_id}/conversions”

Create Gtm Conversion — Set up one conversion: the trigger, the tag, and the record that they belong together.

Permission: google_tag_manager.tag.write

Path parameters: container_id

Request body: GtmConversionCreate

Returns: 201 · GtmConversionRead

GET /api/v1/gtm/containers/{container_id}/snippet

Section titled “GET /api/v1/gtm/containers/{container_id}/snippet”

Gtm Container Snippet — The install snippet, for the developer who has to put it on the site.

Permission: google_tag_manager.container.read

Path parameters: container_id

Returns: 200 · GtmSnippetRead

GET /api/v1/gtm/containers/{container_id}/status

Section titled “GET /api/v1/gtm/containers/{container_id}/status”

Gtm Workspace Status — What is staged in a workspace and not live — the question before every publish.

Permission: google_tag_manager.container.read

Path parameters: container_id

Query parameters:

NameTypeMeaning
workspace_idstring

Returns: 200 · GtmWorkspaceStatusRead

GET /api/v1/gtm/containers/{container_id}/tags

Section titled “GET /api/v1/gtm/containers/{container_id}/tags”

List Gtm Tags — Every tag in a workspace — what is (or is about to be) measuring this client’s site.

Permission: google_tag_manager.container.read

Path parameters: container_id

Query parameters:

NameTypeMeaning
workspace_idstring

Returns: 200 · GtmTagRead[]

POST /api/v1/gtm/containers/{container_id}/tags

Section titled “POST /api/v1/gtm/containers/{container_id}/tags”

Create Gtm Tag — Create a tag from its own type and parameter array.

Permission: google_tag_manager.tag.write

Path parameters: container_id

Query parameters:

NameTypeMeaning
workspace_idstring

Request body: GtmTagWrite

Returns: 201 · GtmTagRead

PATCH /api/v1/gtm/containers/{container_id}/tags/{tag_id}

Section titled “PATCH /api/v1/gtm/containers/{container_id}/tags/{tag_id}”

Update Gtm Tag — Change one tag. Reads it first and writes back under its fingerprint, so an edit somebody made in Tag Manager meanwhile is a 409 rather than a silent overwrite of their work.

Permission: google_tag_manager.tag.write

Path parameters: container_id, tag_id

Query parameters:

NameTypeMeaning
workspace_idstring

Request body: GtmTagUpdate

Returns: 200 · GtmTagRead

DELETE /api/v1/gtm/containers/{container_id}/tags/{tag_id}

Section titled “DELETE /api/v1/gtm/containers/{container_id}/tags/{tag_id}”

Delete Gtm Tag

Permission: google_tag_manager.tag.write

Path parameters: container_id, tag_id

Query parameters:

NameTypeMeaning
workspace_idstring

Returns: 204

GET /api/v1/gtm/containers/{container_id}/triggers

Section titled “GET /api/v1/gtm/containers/{container_id}/triggers”

List Gtm Triggers

Permission: google_tag_manager.container.read

Path parameters: container_id

Query parameters:

NameTypeMeaning
workspace_idstring

Returns: 200 · GtmTriggerRead[]

POST /api/v1/gtm/containers/{container_id}/triggers

Section titled “POST /api/v1/gtm/containers/{container_id}/triggers”

Create Gtm Trigger — Create a trigger from six named kinds rather than from GTM’s own vocabulary.

Permission: google_tag_manager.tag.write

Path parameters: container_id

Query parameters:

NameTypeMeaning
workspace_idstring

Request body: GtmTriggerWrite

Returns: 201 · GtmTriggerRead

DELETE /api/v1/gtm/containers/{container_id}/triggers/{trigger_id}

Section titled “DELETE /api/v1/gtm/containers/{container_id}/triggers/{trigger_id}”

Delete Gtm Trigger

Permission: google_tag_manager.tag.write

Path parameters: container_id, trigger_id

Query parameters:

NameTypeMeaning
workspace_idstring

Returns: 204

GET /api/v1/gtm/containers/{container_id}/variables

Section titled “GET /api/v1/gtm/containers/{container_id}/variables”

List Gtm Variables

Permission: google_tag_manager.container.read

Path parameters: container_id

Query parameters:

NameTypeMeaning
workspace_idstring

Returns: 200 · GtmVariableRead[]

POST /api/v1/gtm/containers/{container_id}/variables

Section titled “POST /api/v1/gtm/containers/{container_id}/variables”

Create Gtm Variable — Create a user-defined variable — a dataLayer read (v), a constant (c), a lookup.

Permission: google_tag_manager.tag.write

Path parameters: container_id

Query parameters:

NameTypeMeaning
workspace_idstring

Request body: GtmVariableWrite

Returns: 201 · GtmVariableRead

DELETE /api/v1/gtm/containers/{container_id}/variables/{variable_id}

Section titled “DELETE /api/v1/gtm/containers/{container_id}/variables/{variable_id}”

Delete Gtm Variable

Permission: google_tag_manager.tag.write

Path parameters: container_id, variable_id

Query parameters:

NameTypeMeaning
workspace_idstring

Returns: 204

POST /api/v1/gtm/containers/{container_id}/verify

Section titled “POST /api/v1/gtm/containers/{container_id}/verify”

Verify Gtm Container — Ask Google what it says about this container, and record the answer either way.

Permission: google_tag_manager.settings.manage

Path parameters: container_id

Returns: 200 · GtmContainerRead

GET /api/v1/gtm/containers/{container_id}/versions

Section titled “GET /api/v1/gtm/containers/{container_id}/versions”

List Gtm Versions — The container’s version history, newest first, with the live one marked.

Permission: google_tag_manager.container.read

Path parameters: container_id

Returns: 200 · GtmVersionRead[]

POST /api/v1/gtm/containers/{container_id}/versions

Section titled “POST /api/v1/gtm/containers/{container_id}/versions”

Create Gtm Version — Freeze what is staged into a version. Still live for nobody — publishing is separate.

Permission: google_tag_manager.tag.write

Path parameters: container_id

Request body: GtmVersionCreate

Returns: 201 · GtmVersionCreated

POST /api/v1/gtm/containers/{container_id}/versions/{version_id}/publish

Section titled “POST /api/v1/gtm/containers/{container_id}/versions/{version_id}/publish”

Publish Gtm Version — Make this version live on the client’s website, now, for every visitor.

Permission: google_tag_manager.version.publish

Path parameters: container_id, version_id

Returns: 200 · GtmPublishResult

GET /api/v1/gtm/containers/{container_id}/workspace

Section titled “GET /api/v1/gtm/containers/{container_id}/workspace”

Read Gtm Workspace — One workspace, whole: its tags, triggers, variables and what of them is staged.

Permission: google_tag_manager.container.read

Path parameters: container_id

Query parameters:

NameTypeMeaning
workspace_idstring

Returns: 200 · GtmWorkspaceContentsRead

GET /api/v1/gtm/containers/{container_id}/workspaces

Section titled “GET /api/v1/gtm/containers/{container_id}/workspaces”

List Gtm Workspaces — The container’s workspaces — its shared drafts. Usually one; sometimes one per person.

Permission: google_tag_manager.container.read

Path parameters: container_id

Returns: 200 · GtmWorkspaceRead[]

List Available Gtm Containers — Search the containers the caller’s own Google grant can reach — a search, not a list.

Permission: google_tag_manager.settings.manage

Query parameters:

NameTypeMeaning
qstringA Tag Manager account name, or a container id such as GTM-XXXXXXX. An id is resolved directly; anything else selects accounts by name and opens those. (default “)

Returns: 200 · GtmPickerRead

Get Gtm Settings — The org’s Tag Manager posture: the write kill switch and the workspace schakl writes in.

Permission: google_tag_manager.settings.manage

Returns: 200 · GtmSettingsRead

Save Gtm Settings

Permission: google_tag_manager.settings.manage

Request body: GtmSettingsWrite

Returns: 200 · GtmSettingsRead

EndpointPermission
GET /reporting/companies/{company_id}/profilereporting.profile.manage
PUT /reporting/companies/{company_id}/profilereporting.profile.manage
GET /reporting/reportsreporting.report.read
GET /reporting/reports/{report_id}reporting.report.read
DELETE /reporting/reports/{report_id}reporting.report.write
PUT /reporting/reports/{report_id}/narrativereporting.report.write
GET /reporting/reports/{report_id}/pdfreporting.report.read
GET /reporting/reports/{report_id}/previewreporting.report.read
POST /reporting/reports/{report_id}/publishreporting.report.send
POST /reporting/reports/{report_id}/rewritereporting.report.write
POST /reporting/reports/{report_id}/sendreporting.report.send
POST /reporting/reports/generatereporting.report.write
POST /reporting/reports/generate-batchreporting.report.write
GET /reporting/settingsreporting.profile.manage
PUT /reporting/settingsreporting.settings.manage
GET /reporting/templatesreporting.profile.manage
POST /reporting/templatesreporting.settings.manage
PUT /reporting/templates/{template_id}reporting.settings.manage
DELETE /reporting/templates/{template_id}reporting.settings.manage
GET /reporting/templates/designs/{design}/sourcereporting.settings.manage
POST /reporting/templates/previewreporting.settings.manage
GET /reporting/templates/sectionsreporting.settings.manage
GET /reporting/tonesreporting.profile.manage
POST /reporting/tonesreporting.settings.manage
PUT /reporting/tones/{tone_id}reporting.settings.manage
DELETE /reporting/tones/{tone_id}reporting.settings.manage
Parameters and schemas per endpoint

GET /api/v1/reporting/companies/{company_id}/profile

Section titled “GET /api/v1/reporting/companies/{company_id}/profile”

Get Profile — The client’s reporting setup. A company that has never had one answers with the inherited defaults rather than a 404 — the form is the same either way.

Permission: reporting.profile.manage

Path parameters: company_id

Returns: 200 · ReportProfileRead

PUT /api/v1/reporting/companies/{company_id}/profile

Section titled “PUT /api/v1/reporting/companies/{company_id}/profile”

Save Profile

Permission: reporting.profile.manage

Path parameters: company_id

Request body: ReportProfileWrite

Returns: 200 · ReportProfileRead

List Reports

Permission: reporting.report.read

Query parameters:

NameTypeMeaning
company_idstring
audienceReportAudience
limitinteger(default 50)
offsetinteger(default 0)
countboolean(default true)

Returns: 200 · ReportList

Get Report

Permission: reporting.report.read

Path parameters: report_id

Returns: 200 · ReportDetail

DELETE /api/v1/reporting/reports/{report_id}

Section titled “DELETE /api/v1/reporting/reports/{report_id}”

Delete Report

Permission: reporting.report.write

Path parameters: report_id

Returns: 204

PUT /api/v1/reporting/reports/{report_id}/narrative

Section titled “PUT /api/v1/reporting/reports/{report_id}/narrative”

Edit Narrative — Hand-edit the prose before it goes out — the point of review-before-send.

Permission: reporting.report.write

Path parameters: report_id

Request body: ReportNarrativeUpdate

Returns: 200 · ReportDetail

GET /api/v1/reporting/reports/{report_id}/pdf

Section titled “GET /api/v1/reporting/reports/{report_id}/pdf”

Report Pdf — The stored document. Loaded through the portal-aware repository, so a client can download exactly the reports they can open — and never the internal analysis.

Permission: reporting.report.read

Path parameters: report_id

Returns: 200

GET /api/v1/reporting/reports/{report_id}/preview

Section titled “GET /api/v1/reporting/reports/{report_id}/preview”

Preview Report — The document as HTML — the same artefact the PDF prints, so the two cannot drift.

Permission: reporting.report.read

Path parameters: report_id

Returns: 200

POST /api/v1/reporting/reports/{report_id}/publish

Section titled “POST /api/v1/reporting/reports/{report_id}/publish”

Publish Report

Permission: reporting.report.send

Path parameters: report_id

Query parameters:

NameTypeMeaning
publishedboolean(default true)

Returns: 200 · ReportDetail

POST /api/v1/reporting/reports/{report_id}/rewrite

Section titled “POST /api/v1/reporting/reports/{report_id}/rewrite”

Rewrite Section

Permission: reporting.report.write

Path parameters: report_id

Request body: ReportRewriteRequest

Returns: 200 · ReportDetail

POST /api/v1/reporting/reports/{report_id}/send

Section titled “POST /api/v1/reporting/reports/{report_id}/send”

Send Report

Permission: reporting.report.send

Path parameters: report_id

Request body: ReportSendRequest

Returns: 200 · ReportDetail

Generate Report — Queue a run. Never generates inline — it calls several APIs and a model.

Permission: reporting.report.write

Request body: ReportRunRequest

Returns: 200 · ReportActionResult

POST /api/v1/reporting/reports/generate-batch

Section titled “POST /api/v1/reporting/reports/generate-batch”

Generate Batch

Permission: reporting.report.write

Request body: ReportRunBatchRequest

Returns: 200 · ReportRunBatchResult

Get Settings

Permission: reporting.profile.manage

Returns: 200 · ReportingSettingsRead

Save Settings

Permission: reporting.settings.manage

Request body: ReportingSettingsWrite

Returns: 200 · ReportingSettingsRead

List Templates

Permission: reporting.profile.manage

Query parameters:

NameTypeMeaning
audienceReportAudience

Returns: 200 · ReportTemplateRead[]

Create Template

Permission: reporting.settings.manage

Request body: ReportTemplateWrite

Returns: 201 · ReportTemplateRead

PUT /api/v1/reporting/templates/{template_id}

Section titled “PUT /api/v1/reporting/templates/{template_id}”

Update Template

Permission: reporting.settings.manage

Path parameters: template_id

Request body: ReportTemplateWrite

Returns: 200 · ReportTemplateRead

DELETE /api/v1/reporting/templates/{template_id}

Section titled “DELETE /api/v1/reporting/templates/{template_id}”

Delete Template

Permission: reporting.settings.manage

Path parameters: template_id

Returns: 204

GET /api/v1/reporting/templates/designs/{design}/source

Section titled “GET /api/v1/reporting/templates/designs/{design}/source”

Template Source — A shipped design’s own HTML and CSS, to start a custom report template from.

Permission: reporting.settings.manage

Path parameters: design

Returns: 200 · ReportTemplateSource

Preview Template — Render an unsaved template — the editor’s live preview.

Permission: reporting.settings.manage

Request body: ReportTemplatePreviewRequest

Returns: 200

Section Catalog — What a template may order or switch off — the registry, made visible (§15’s “registry, not free text”, applied to design).

Permission: reporting.settings.manage

Returns: 200 · SectionCatalogEntry[]

List Tones — Readable by whoever assigns one to a client, editable only by an admin (the service re-checks) — a manager must be able to see what voice they are picking.

Permission: reporting.profile.manage

Returns: 200 · ReportToneRead[]

Create Tone

Permission: reporting.settings.manage

Request body: ReportToneWrite

Returns: 201 · ReportToneRead

Update Tone

Permission: reporting.settings.manage

Path parameters: tone_id

Request body: ReportToneWrite

Returns: 200 · ReportToneRead

Delete Tone

Permission: reporting.settings.manage

Path parameters: tone_id

Returns: 204