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.
marketing
Section titled “marketing”| Endpoint | Permission |
|---|---|
GET /marketing/accounts | marketing.link.manage |
GET /marketing/clients | marketing.metrics.read |
GET /marketing/companies/{company_id}/ai-search | marketing.metrics.read |
POST /marketing/companies/{company_id}/ai-search/brand | marketing.link.manage |
POST /marketing/companies/{company_id}/ai-search/refresh | marketing.link.manage |
PUT /marketing/companies/{company_id}/ai-search/settings | marketing.link.manage |
GET /marketing/companies/{company_id}/drilldown | marketing.metrics.read |
GET /marketing/companies/{company_id}/leads | marketing.metrics.read |
GET /marketing/companies/{company_id}/leads/catalog | marketing.link.manage |
GET /marketing/companies/{company_id}/metrics | marketing.metrics.read |
GET /marketing/companies/{company_id}/settings | marketing.metrics.read |
PUT /marketing/companies/{company_id}/settings | marketing.link.manage |
GET /marketing/companies/{company_id}/websites | marketing.link.manage |
GET /marketing/links | marketing.metrics.read |
POST /marketing/links | marketing.link.manage |
DELETE /marketing/links/{link_id} | marketing.link.manage |
POST /marketing/links/{link_id}/ai-visibility/import | marketing.link.manage |
POST /marketing/links/{link_id}/ai-visibility/rows | marketing.link.manage |
GET /marketing/overview | marketing.overview.read |
GET /marketing/settings | marketing.link.manage |
PUT /marketing/settings | marketing.link.manage |
GET /marketing/settings/seranking/check | marketing.link.manage |
GET /marketing/summary | marketing.metrics.read |
Parameters and schemas per endpoint
GET /api/v1/marketing/accounts
Section titled “GET /api/v1/marketing/accounts”Available Accounts — The accounts/properties/sites the caller’s connection can reach for source.
Permission: marketing.link.manage
Query parameters:
| Name | Type | Meaning |
|---|---|---|
source | MarketingSource · required | |
website_id | string | Required for a source whose credential is per website (rankmath); ignored by every other source. |
refresh | boolean | Skip 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
GET /api/v1/marketing/clients
Section titled “GET /api/v1/marketing/clients”Linked Clients — The clients that actually have a source linked, with which sources and how each is doing.
Permission: marketing.metrics.read
Query parameters:
| Name | Type | Meaning |
|---|---|---|
limit | integer | (default 200) |
Returns: 200 · MarketingClientList
GET /api/v1/marketing/companies/{company_id}/ai-search
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:
| Name | Type | Meaning |
|---|---|---|
link_id | string · required | |
kind | string · required | |
range_days | integer | (default 30) |
period | string | The 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:
| Name | Type | Meaning |
|---|---|---|
period | string | The 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. |
f | string[] | 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:
| Name | Type | Meaning |
|---|---|---|
range_days | integer | (default 30) |
period | string | The 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[]
GET /api/v1/marketing/links
Section titled “GET /api/v1/marketing/links”List Links
Permission: marketing.metrics.read
Query parameters:
| Name | Type | Meaning |
|---|---|---|
company_id | string · required |
Returns: 200 · app__modules__marketing__schemas__LinkRead[]
POST /api/v1/marketing/links
Section titled “POST /api/v1/marketing/links”Create Link
Permission: marketing.link.manage
Request body: app__modules__marketing__schemas__LinkCreate
Returns: 201 · app__modules__marketing__schemas__LinkRead
DELETE /api/v1/marketing/links/{link_id}
Section titled “DELETE /api/v1/marketing/links/{link_id}”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
GET /api/v1/marketing/overview
Section titled “GET /api/v1/marketing/overview”Overview — The morning-coffee grid: one row per linked client, from stored data, server-sorted.
Permission: marketing.overview.read
Query parameters:
| Name | Type | Meaning |
|---|---|---|
range_days | integer | (default 30) |
sort | string | company_name | sessions | clicks | position | cost | conversions (- = desc) |
period | string | The 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 /api/v1/marketing/settings
Section titled “GET /api/v1/marketing/settings”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
PUT /api/v1/marketing/settings
Section titled “PUT /api/v1/marketing/settings”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
GET /api/v1/marketing/summary
Section titled “GET /api/v1/marketing/summary”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:
| Name | Type | Meaning |
|---|---|---|
range_days | integer | (default 30) |
limit | integer | (default 5) |
period | string | The 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
google_ads
Section titled “google_ads”| Endpoint | Permission |
|---|---|
GET /google-ads/accounts | google_ads.account.read |
POST /google-ads/accounts | google_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-groups | google_ads.account.read |
POST /google-ads/accounts/{account_id}/ad-groups | google_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}/ads | google_ads.account.read |
POST /google-ads/accounts/{account_id}/ads | google_ads.campaign.write |
PATCH /google-ads/accounts/{account_id}/ads | google_ads.campaign.write |
POST /google-ads/accounts/{account_id}/budgets | google_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}/campaigns | google_ads.account.read |
POST /google-ads/accounts/{account_id}/campaigns | google_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}/changes | google_ads.account.read |
GET /google-ads/accounts/{account_id}/conversions | google_ads.account.read |
GET /google-ads/accounts/{account_id}/decisions | google_ads.account.read |
POST /google-ads/accounts/{account_id}/decisions | google_ads.policy.manage |
DELETE /google-ads/accounts/{account_id}/decisions/{decision_id} | google_ads.policy.manage |
GET /google-ads/accounts/{account_id}/devices | google_ads.account.read |
GET /google-ads/accounts/{account_id}/geo | google_ads.account.read |
POST /google-ads/accounts/{account_id}/keyword-ideas | google_ads.account.read |
GET /google-ads/accounts/{account_id}/keywords | google_ads.account.read |
POST /google-ads/accounts/{account_id}/keywords | google_ads.keyword.write |
PATCH /google-ads/accounts/{account_id}/keywords | google_ads.keyword.write |
POST /google-ads/accounts/{account_id}/keywords/remove | google_ads.keyword.write |
POST /google-ads/accounts/{account_id}/negative-lists | google_ads.negative.write |
DELETE /google-ads/accounts/{account_id}/negative-lists/{shared_set_id} | google_ads.negative.write |
GET /google-ads/accounts/{account_id}/negatives | google_ads.account.read |
POST /google-ads/accounts/{account_id}/negatives | google_ads.negative.write |
POST /google-ads/accounts/{account_id}/negatives/remove | google_ads.negative.write |
GET /google-ads/accounts/{account_id}/policy | google_ads.policy.manage |
PUT /google-ads/accounts/{account_id}/policy | google_ads.policy.manage |
DELETE /google-ads/accounts/{account_id}/policy | google_ads.policy.manage |
POST /google-ads/accounts/{account_id}/query | google_ads.query.run |
GET /google-ads/accounts/{account_id}/recommendations | google_ads.account.read |
GET /google-ads/accounts/{account_id}/search-terms | google_ads.account.read |
GET /google-ads/accounts/{account_id}/snapshot | google_ads.account.read |
GET /google-ads/accounts/{account_id}/trend | google_ads.account.read |
POST /google-ads/accounts/{account_id}/verify | google_ads.settings.manage |
GET /google-ads/accounts/available | google_ads.settings.manage |
GET /google-ads/policy | google_ads.policy.manage |
PUT /google-ads/policy | google_ads.policy.manage |
DELETE /google-ads/policy | google_ads.policy.manage |
GET /google-ads/settings | google_ads.settings.manage |
PUT /google-ads/settings | google_ads.settings.manage |
Parameters and schemas per endpoint
GET /api/v1/google-ads/accounts
Section titled “GET /api/v1/google-ads/accounts”List Google Ads Accounts — Every linked Google Ads account this caller may see — start here.
Permission: google_ads.account.read
Query parameters:
| Name | Type | Meaning |
|---|---|---|
company_id | string | |
active_only | boolean | (default false) |
Returns: 200 · GoogleAdsAccountRead[]
POST /api/v1/google-ads/accounts
Section titled “POST /api/v1/google-ads/accounts”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:
| Name | Type | Meaning |
|---|---|---|
campaigns | string[] | |
include_removed | boolean | (default false) |
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
status | string | Only rows with this Google status: ENABLED, PAUSED or REMOVED. REMOVED implies include_removed, because a filter that always answers nothing is not a filter. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
validate_only | boolean | Check 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:
| Name | Type | Meaning |
|---|---|---|
validate_only | boolean | Check 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:
| Name | Type | Meaning |
|---|---|---|
campaigns | string[] | |
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
status | string | Only rows with this Google status: ENABLED, PAUSED or REMOVED. REMOVED implies include_removed, because a filter that always answers nothing is not a filter. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
validate_only | boolean | Check 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:
| Name | Type | Meaning |
|---|---|---|
campaigns | string[] | Filter by campaign name, case-insensitive substring match. |
include_removed | boolean | Include 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) |
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
status | string | Only rows with this Google status: ENABLED, PAUSED or REMOVED. REMOVED implies include_removed, because a filter that always answers nothing is not a filter. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
validate_only | boolean | Check 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:
| Name | Type | Meaning |
|---|---|---|
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
subject_type | string | |
decision | string | |
include_withdrawn | boolean | (default false) |
limit | integer | (default 50) |
offset | integer | (default 0) |
count | boolean | Set 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:
| Name | Type | Meaning |
|---|---|---|
campaigns | string[] | |
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
campaigns | string[] | |
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
campaigns | string[] | |
include_removed | boolean | (default false) |
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
status | string | Only rows with this Google status: ENABLED, PAUSED or REMOVED. REMOVED implies include_removed, because a filter that always answers nothing is not a filter. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
validate_only | boolean | Check 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:
| Name | Type | Meaning |
|---|---|---|
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
campaigns | string[] | |
min_cost | number | Only terms that cost at least this, in account currency. |
min_clicks | integer | |
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-MM-DD, inclusive. |
q | string | Free 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. |
limit | integer | How 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. |
offset | integer | Where 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:
| Name | Type | Meaning |
|---|---|---|
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-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:
| Name | Type | Meaning |
|---|---|---|
compare | string | What to compare against: ‘year’ (the same period a year earlier, the default) or ‘previous’ (the period immediately before). |
period | string | A 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_from | string | YYYY-MM-DD, inclusive. |
date_to | string | YYYY-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
GET /api/v1/google-ads/accounts/available
Section titled “GET /api/v1/google-ads/accounts/available”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 /api/v1/google-ads/policy
Section titled “GET /api/v1/google-ads/policy”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
PUT /api/v1/google-ads/policy
Section titled “PUT /api/v1/google-ads/policy”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
DELETE /api/v1/google-ads/policy
Section titled “DELETE /api/v1/google-ads/policy”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 /api/v1/google-ads/settings
Section titled “GET /api/v1/google-ads/settings”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
PUT /api/v1/google-ads/settings
Section titled “PUT /api/v1/google-ads/settings”Save Google Ads Settings
Permission: google_ads.settings.manage
Request body: GoogleAdsSettingsWrite
Returns: 200 · GoogleAdsSettingsRead
google_tag_manager
Section titled “google_tag_manager”| Endpoint | Permission |
|---|---|
GET /gtm/containers | google_tag_manager.container.read |
POST /gtm/containers | google_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}/conversions | google_tag_manager.container.read |
POST /gtm/containers/{container_id}/conversions | google_tag_manager.tag.write |
GET /gtm/containers/{container_id}/snippet | google_tag_manager.container.read |
GET /gtm/containers/{container_id}/status | google_tag_manager.container.read |
GET /gtm/containers/{container_id}/tags | google_tag_manager.container.read |
POST /gtm/containers/{container_id}/tags | google_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}/triggers | google_tag_manager.container.read |
POST /gtm/containers/{container_id}/triggers | google_tag_manager.tag.write |
DELETE /gtm/containers/{container_id}/triggers/{trigger_id} | google_tag_manager.tag.write |
GET /gtm/containers/{container_id}/variables | google_tag_manager.container.read |
POST /gtm/containers/{container_id}/variables | google_tag_manager.tag.write |
DELETE /gtm/containers/{container_id}/variables/{variable_id} | google_tag_manager.tag.write |
POST /gtm/containers/{container_id}/verify | google_tag_manager.settings.manage |
GET /gtm/containers/{container_id}/versions | google_tag_manager.container.read |
POST /gtm/containers/{container_id}/versions | google_tag_manager.tag.write |
POST /gtm/containers/{container_id}/versions/{version_id}/publish | google_tag_manager.version.publish |
GET /gtm/containers/{container_id}/workspace | google_tag_manager.container.read |
GET /gtm/containers/{container_id}/workspaces | google_tag_manager.container.read |
GET /gtm/containers/available | google_tag_manager.settings.manage |
GET /gtm/settings | google_tag_manager.settings.manage |
PUT /gtm/settings | google_tag_manager.settings.manage |
Parameters and schemas per endpoint
GET /api/v1/gtm/containers
Section titled “GET /api/v1/gtm/containers”List Gtm Containers — Every linked Tag Manager container this caller may see — start here.
Permission: google_tag_manager.container.read
Query parameters:
| Name | Type | Meaning |
|---|---|---|
company_id | string | |
active_only | boolean | (default false) |
Returns: 200 · GtmContainerRead[]
POST /api/v1/gtm/containers
Section titled “POST /api/v1/gtm/containers”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 /api/v1/gtm/containers/{container_id}
Section titled “GET /api/v1/gtm/containers/{container_id}”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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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:
| Name | Type | Meaning |
|---|---|---|
workspace_id | string |
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[]
GET /api/v1/gtm/containers/available
Section titled “GET /api/v1/gtm/containers/available”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:
| Name | Type | Meaning |
|---|---|---|
q | string | A 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 /api/v1/gtm/settings
Section titled “GET /api/v1/gtm/settings”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
PUT /api/v1/gtm/settings
Section titled “PUT /api/v1/gtm/settings”Save Gtm Settings
Permission: google_tag_manager.settings.manage
Request body: GtmSettingsWrite
Returns: 200 · GtmSettingsRead
reporting
Section titled “reporting”| Endpoint | Permission |
|---|---|
GET /reporting/companies/{company_id}/profile | reporting.profile.manage |
PUT /reporting/companies/{company_id}/profile | reporting.profile.manage |
GET /reporting/reports | reporting.report.read |
GET /reporting/reports/{report_id} | reporting.report.read |
DELETE /reporting/reports/{report_id} | reporting.report.write |
PUT /reporting/reports/{report_id}/narrative | reporting.report.write |
GET /reporting/reports/{report_id}/pdf | reporting.report.read |
GET /reporting/reports/{report_id}/preview | reporting.report.read |
POST /reporting/reports/{report_id}/publish | reporting.report.send |
POST /reporting/reports/{report_id}/rewrite | reporting.report.write |
POST /reporting/reports/{report_id}/send | reporting.report.send |
POST /reporting/reports/generate | reporting.report.write |
POST /reporting/reports/generate-batch | reporting.report.write |
GET /reporting/settings | reporting.profile.manage |
PUT /reporting/settings | reporting.settings.manage |
GET /reporting/templates | reporting.profile.manage |
POST /reporting/templates | reporting.settings.manage |
PUT /reporting/templates/{template_id} | reporting.settings.manage |
DELETE /reporting/templates/{template_id} | reporting.settings.manage |
GET /reporting/templates/designs/{design}/source | reporting.settings.manage |
POST /reporting/templates/preview | reporting.settings.manage |
GET /reporting/templates/sections | reporting.settings.manage |
GET /reporting/tones | reporting.profile.manage |
POST /reporting/tones | reporting.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
GET /api/v1/reporting/reports
Section titled “GET /api/v1/reporting/reports”List Reports
Permission: reporting.report.read
Query parameters:
| Name | Type | Meaning |
|---|---|---|
company_id | string | |
audience | ReportAudience | |
limit | integer | (default 50) |
offset | integer | (default 0) |
count | boolean | (default true) |
Returns: 200 · ReportList
GET /api/v1/reporting/reports/{report_id}
Section titled “GET /api/v1/reporting/reports/{report_id}”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:
| Name | Type | Meaning |
|---|---|---|
published | boolean | (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
POST /api/v1/reporting/reports/generate
Section titled “POST /api/v1/reporting/reports/generate”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 /api/v1/reporting/settings
Section titled “GET /api/v1/reporting/settings”Get Settings
Permission: reporting.profile.manage
Returns: 200 · ReportingSettingsRead
PUT /api/v1/reporting/settings
Section titled “PUT /api/v1/reporting/settings”Save Settings
Permission: reporting.settings.manage
Request body: ReportingSettingsWrite
Returns: 200 · ReportingSettingsRead
GET /api/v1/reporting/templates
Section titled “GET /api/v1/reporting/templates”List Templates
Permission: reporting.profile.manage
Query parameters:
| Name | Type | Meaning |
|---|---|---|
audience | ReportAudience |
Returns: 200 · ReportTemplateRead[]
POST /api/v1/reporting/templates
Section titled “POST /api/v1/reporting/templates”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
POST /api/v1/reporting/templates/preview
Section titled “POST /api/v1/reporting/templates/preview”Preview Template — Render an unsaved template — the editor’s live preview.
Permission: reporting.settings.manage
Request body: ReportTemplatePreviewRequest
Returns: 200
GET /api/v1/reporting/templates/sections
Section titled “GET /api/v1/reporting/templates/sections”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[]
GET /api/v1/reporting/tones
Section titled “GET /api/v1/reporting/tones”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[]
POST /api/v1/reporting/tones
Section titled “POST /api/v1/reporting/tones”Create Tone
Permission: reporting.settings.manage
Request body: ReportToneWrite
Returns: 201 · ReportToneRead
PUT /api/v1/reporting/tones/{tone_id}
Section titled “PUT /api/v1/reporting/tones/{tone_id}”Update Tone
Permission: reporting.settings.manage
Path parameters: tone_id
Request body: ReportToneWrite
Returns: 200 · ReportToneRead
DELETE /api/v1/reporting/tones/{tone_id}
Section titled “DELETE /api/v1/reporting/tones/{tone_id}”Delete Tone
Permission: reporting.settings.manage
Path parameters: tone_id
Returns: 204