Everything the MCP server can do, plus listing reports and exporting the audit trail, over plain HTTPS. The base URL is https://api.glitchads.ai/v1, and the machine readable schema is at GET /v1/openapi.json.
| Authentication | Authorization: Bearer <token> on every request: an API key (glads_live_…) or an OAuth access token. Never in a query string. |
| Content type | JSON in, JSON out. Content-Type: application/json on any request carrying a body. |
| Organization context | Explicit in the path, /organizations/{org}/…, where {org} is the slug. API keys may use _ for their own organization; OAuth tokens cover all the user's organizations and must name one. Your membership is checked on every request, and a slug the credential cannot act in, or one that does not exist, answers org_access_denied. |
| Money and dates | Amounts are numbers in the organization currency with up to two decimal places, so 40.00 arrives as 40.0. A campaign budget is a daily amount in whole units: a budget you send is rounded to a whole number. Target CPA keeps cents. Dates as YYYY-MM-DD, timestamps ISO 8601 in UTC. |
| Pagination | ?limit= defaults to 20 and accepts 1 to 100; anything else is a validation_error. Lists carry total and next_cursor, null when done; pass it back as ?cursor=. The organization list and the audit export return everything in one response. |
| Writes preview first | Endpoints that change a running campaign take dry_run, which defaults to true: the first call returns a preview and changes nothing. Execute with "dry_run": false. An executed write that finishes at once returns an action_id that undo reverses; work that continues in the background returns a task_tracking_id, and a state change returns both. Campaign creation, ad creation and report generation have no dry_run and execute immediately. Creation and reports only produce drafts and documents, but a new ad on a live campaign goes to Google Ads and starts serving, and it returns no action_id: remove an ad in the dashboard. |
| Asynchronous work | Campaign creation, ad creation and report generation answer 202 Accepted with a task_tracking_id; an executed state change answers 200 and carries one too. Poll GET /task-tracking/{id}. |
| Errors and limits | One structured envelope, X-RateLimit-* headers, 429 with Retry-After, and a daily write quota shared by the organization. The details are on the errors page. |
The version is in the path. Changes within v1 are additive: new fields, new optional parameters, new endpoints. Read responses so that an unfamiliar field is ignored rather than fatal.
Anything that would break a working client arrives as a new prefix alongside this one, not as a change inside v1. /v1/openapi.json is generated from the running service, so its endpoints, fields and types are always current. Accepted values and limits are not in it as enums; this page lists them.
/organizations
The organizations this credential can act in, each with your role, plan and currency. Exactly one for an API key; every organization the user belongs to for an OAuth token. Glitch adops staff also see every client organization assigned to their team, read only: those rows carry access adops_team and no role. adops_team names the team responsible for an organization on any row that has one, so results can be grouped by team.
// GET /v1/organizations
{
"organizations": [
{ "slug": "acme", "name": "Acme Ltd", "role": "owner", "plan": "SMB", "currency": "EUR",
"access": "membership", "adops_team": null },
{ "slug": "bright-dental", "name": "Bright Dental", "role": null, "plan": "SMB", "currency": "GBP",
"access": "adops_team", "adops_team": { "name": "Realglitch" } }
],
"total": 2,
"next_cursor": null
}
/organizations/{org}
Plan, currency, spend limits and ad accounts. max_spend is the organization's monthly spend cap, null when none is set, and api_budget_change_limit_pct is the most one call may move a budget. ad_accounts lists the Google Ads accounts the organization's campaigns use or its members have selected, each linked, pending or not_linked. access says how this credential reaches the organization, membership or adops_team, and adops_team names the Glitch team responsible for it, null when there is none.
// GET /v1/organizations/acme
{
"slug": "acme", "name": "Acme Ltd", "plan": "SMB", "currency": "EUR",
"max_spend": 5000.00, "api_budget_change_limit_pct": 25,
"ad_accounts": [ { "id": "8932210042", "platform": "google", "status": "linked" } ],
"access": "membership", "adops_team": null
}
/organizations/{org}/business-profile
The business context Glitch has learned: its URL, brand name, description, products, ideal customer profiles, competitors and trust signals. Answers not_found until the organization has a business profile.
/organizations/{org}/campaigns
List campaigns with their seven day numbers.
| Parameter | Type | Notes |
|---|---|---|
account_id |
Typestring | Filter to one ad account. |
state |
Typestring | live, paused, draft or error. |
limit, cursor |
Type | Pagination. |
curl "https://api.glitchads.ai/v1/organizations/acme/campaigns?state=live" \
-H "Authorization: Bearer $GLITCH_API_KEY"
// 200
{
"campaigns": [
{ "slug": "acme-prospecting", "name": "Prospecting", "type": "prospecting", "state": "live",
"budget": 40.00, "spend_7d": 212.40, "conversions_7d": 18, "cpa_7d": 11.80,
"created_at": "2026-05-02T10:11:04Z" }
],
"total": 4, "next_cursor": null
}
/organizations/{org}/campaigns/{slug}
Full campaign detail. keywords_count includes paused keywords. negative_keywords_count counts the active exclusions on the campaign and on its ads, including ones added through this API. last_synced_at is null for a campaign that has never been in Google Ads.
// GET /v1/organizations/acme/campaigns/acme-prospecting
{
"slug": "acme-prospecting", "name": "Prospecting", "type": "prospecting", "state": "live",
"budget": 40.00, "target_cpa": 15.00, "goal": "prospecting", "website_url": "https://acme.co",
"ads_count": 3, "keywords_count": 84, "negative_keywords_count": 31,
"last_synced_at": "2026-07-08T06:00:12Z"
}
/organizations/{org}/campaigns
Generate a complete campaign from a website. Always a draft: going live is a separate state call. Asynchronous. The draft starts from the business profile's description and target locations, in the Google Ads account selected for the organization. It is refused while the business profile is still being analysed, or when no Google Ads account is selected for the organization.
The response names the new draft's campaign slug and its url, the campaign's page in Glitch. The link opens the campaign in its own organization, even for someone who last worked in another one.
| Parameter | Type | Notes |
|---|---|---|
website_url |
Typestring, required | The site to build from. |
type |
Typestring | prospecting, the default, or brand. A brand campaign's keywords are also added as negatives to the account's unpublished draft campaigns, so those never compete for the brand's searches; campaigns already live get the same protection from the brand-leak check once the brand campaign is live. The other campaign types Glitch builds are not creatable here: hipo, location and icp come from applying a recommendation, and competitor needs a competitor selection this body cannot express. |
name |
Typestring | Kept as sent. Left out, Glitch names the campaign from the business, its type and its locations once generation finishes. |
budget |
Typenumber | Daily budget for the draft, in whole units. Checked against the organization's monthly spend cap. |
curl -X POST https://api.glitchads.ai/v1/organizations/acme/campaigns \
-H "Authorization: Bearer $GLITCH_API_KEY" -H "Content-Type: application/json" \
-d '{"website_url":"https://acme.co","type":"brand","budget":25.00}'
// 202
{ "task_tracking_id": 5521, "status": "queued", "campaign": "Xk2pQ9LmT4",
"url": "https://app.glitchads.ai/campaigns/google/Xk2pQ9LmT4?org=acme" }
/organizations/{org}/campaigns/{slug}/ads
Add a new ad (a Google ad group) to an existing campaign, generated from a landing page. Asynchronous, with no dry_run. On a live campaign the finished ad is pushed to Google Ads and starts serving; on a draft it waits with the campaign. It returns the new ad_id but no action_id: remove an ad in the dashboard. Imported external campaigns and competitor campaigns are not accepted, and neither is a campaign with no linked Google Ads account or an incomplete Google connection.
| Parameter | Type | Notes |
|---|---|---|
url |
Typestring, required | The landing page the ad is generated from. Must be on the campaign website's domain (or a subdomain of it). |
name |
Typestring | Numbered after the existing ads (Ad 2, Ad 3) if you leave it out. |
curl -X POST https://api.glitchads.ai/v1/organizations/acme/campaigns/acme-prospecting/ads \
-H "Authorization: Bearer $GLITCH_API_KEY" -H "Content-Type: application/json" \
-d '{"url":"https://acme.co/pricing"}'
// 202
{ "task_tracking_id": 5533, "status": "queued", "ad_id": 912 }
/organizations/{org}/campaigns/{slug}
Update name, budget or target CPA. Only the fields you send change. One call may move a budget by at most api_budget_change_limit_pct, 25 by default; a breach answers budget_cap_exceeded with allowed_min and allowed_max. Where the organization has a max_spend, the daily budgets of all its campaigns, drafts and paused ones included, times 30 must stay within it; a breach answers budget_cap_exceeded with monthly_cap and allowed_max_daily. A dry run reports either breach as within_limits: false instead.
| Parameter | Type | Notes |
|---|---|---|
name |
Typestring | New name. |
budget |
Typenumber | New daily budget, in whole units. |
target_cpa |
Typenumber | New target CPA. |
dry_run |
Typeboolean | Defaults to true. |
# preview: dry_run defaults to true, so this changes nothing
curl -X PATCH https://api.glitchads.ai/v1/organizations/acme/campaigns/acme-prospecting \
-H "Authorization: Bearer $GLITCH_API_KEY" -H "Content-Type: application/json" \
-d '{"budget":48.00}'
// 200 (dry run)
{ "dry_run": true, "would_change": { "budget": { "from": 40.00, "to": 48.00 } },
"within_limits": true }
// 200 (executed, with "dry_run": false)
{ "applied": true, "action_id": "act_9077",
"changed": { "budget": { "from": 40.00, "to": 48.00 } } }
/organizations/{org}/campaigns/{slug}/state
Pause, resume or launch. An executed change syncs to Google Ads in the background through the same pipeline as the dashboard, and answers straight away with a task_tracking_id to poll and an action_id to undo. The campaign must be linked to a Google Ads account the credential's user can access.
Launching refuses a campaign with no budget, and one whose budget would take the organization over its monthly spend cap. It also checks Google Ads for changes made there since the last sync and answers sync_conflict if it finds any; the check covers syncs in the last 30 days and is skipped if Google Ads cannot be read. A campaign's first launch also runs the checks the dashboard runs before approval: a location or radius, valid sitelinks and callouts, at least one ad with valid headlines and descriptions, and a keyword on every ad. The error names the first thing missing. A dry run runs the same checks and reports the first one that fails as blocked_by, which is null when the transition would go through; the sync conflict check runs only on the executed call.
| Parameter | Type | Notes |
|---|---|---|
state |
Typestring, required | live, paused or draft. |
dry_run |
Typeboolean | Defaults to true. |
// 200 (dry run)
{ "dry_run": true,
"would_change": { "state": { "from": "draft", "to": "live" } },
"blocked_by": "Set a daily budget greater than 0 before launching." }
// 200 (executed)
{ "accepted": true, "task_tracking_id": 6102, "action_id": "act_9120" }
/organizations/{org}/campaigns/{slug}/keywords
The keywords on a campaign, with thirty day performance.
| Parameter | Type | Notes |
|---|---|---|
state |
Typestring | enabled or paused. |
sort |
Typestring | cost by default, or clicks or conversions. |
limit, cursor |
Type | Pagination. |
// 200
{
"keywords": [
{ "text": "ppc management", "match_type": "phrase", "state": "enabled",
"clicks_30d": 140, "cost_30d": 168.20, "conversions_30d": 11, "avg_cpc": 1.20 }
],
"total": 84, "next_cursor": "djE6MjA="
}
/organizations/{org}/campaigns/{slug}/negative-keywords
Exclude terms across the campaign. On a live campaign the exclusions go to Google Ads first; on a draft they wait with the campaign.
| Parameter | Type | Notes |
|---|---|---|
keywords |
Typestring[], required | 1 to 100 terms, each up to 80 characters. |
match_type |
Typestring | phrase by default, or exact or broad. |
dry_run |
Typeboolean | Defaults to true. |
curl -X POST https://api.glitchads.ai/v1/organizations/acme/campaigns/acme-prospecting/negative-keywords \
-H "Authorization: Bearer $GLITCH_API_KEY" -H "Content-Type: application/json" \
-d '{"keywords":["free ads course","ppc jobs"]}'
// 200 (dry run, the default)
{ "dry_run": true, "match_type": "phrase",
"would_apply": [ { "op": "add_negative_keyword", "term": "free ads course", "match_type": "phrase" },
{ "op": "add_negative_keyword", "term": "ppc jobs", "match_type": "phrase" } ] }
// 200 (executed, with "dry_run": false)
{ "applied": true, "action_id": "act_9124", "added": 2, "match_type": "phrase" }
/organizations/{org}/performance
Metrics over a date range, grouped and compared against the previous period.
| Parameter | Type | Notes |
|---|---|---|
from, to |
Typedate | By default, the seven days up to the latest day with data, usually yesterday. to alone gives the seven days ending then; from alone runs to the latest day with data. |
campaign |
Typestring | Restrict to one campaign slug; an unknown slug is not_found. |
group_by |
Typestring | campaign by default, returned as by_campaign, highest spend first. day returns by_day rows with a date, and week returns by_week rows with a week_start, a Monday. Each row carries spend, conversions and CPA. |
compare |
Typeboolean | true by default: vs_previous gives the change against the period of the same length just before, each value null when that period had no data. |
curl "https://api.glitchads.ai/v1/organizations/acme/performance?from=2026-07-01&to=2026-07-07" \
-H "Authorization: Bearer $GLITCH_API_KEY"
// 200
{
"period": { "from": "2026-07-01", "to": "2026-07-07" },
"summary": { "spend": 412.10, "impressions": 18740, "clicks": 1143, "ctr": 0.061,
"conversions": 31, "cpa": 13.29, "roas": 4.2,
"vs_previous": { "spend": "+4%", "conversions": "+12%", "cpa": "-8%" } },
"by_campaign": [
{ "slug": "acme-brand", "spend": 120.20, "conversions": 19, "cpa": 6.33 },
{ "slug": "acme-prospecting", "spend": 212.40, "conversions": 10, "cpa": 21.24 }
]
}
/organizations/{org}/search-terms
What people actually searched, dearest first.
| Parameter | Type | Notes |
|---|---|---|
campaign |
Typestring | Restrict to one campaign slug; an unknown slug is not_found. |
days |
Typeinteger | 1 to 90, 30 by default, counted back from the latest day with data. |
min_cost |
Typenumber | Only terms costing at least this much. |
limit, cursor |
Type | Pagination. |
// 200
{
"search_terms": [
{ "term": "ppc management for dentists", "campaign": "acme-prospecting",
"clicks": 31, "cost": 42.10, "conversions": 4 },
{ "term": "free ads course", "campaign": "acme-prospecting",
"clicks": 22, "cost": 18.60, "conversions": 0 }
],
"total": 412, "next_cursor": "djE6MjA="
}
Five types can be read: weekly, strategic, intelligence, performance and competitor. Report bodies come back as markdown.
Only weekly can be generated through the API today. The others are produced in Glitch, on a schedule or on request, and read back here.
/organizations/{org}/reports/{type}
List reports of a type, newest first. Each row carries the id, type and generated_at; read one to get its body.
/organizations/{org}/reports/{type}/latest
The most recent one, without listing first. not_found when there is none yet.
// GET /v1/organizations/acme/reports/weekly/latest
{
"id": "rep_2841", "type": "weekly", "generated_at": "2026-07-06T07:00:00Z",
"markdown": "# Weekly performance\n\nSpend 412.10 EUR (+4%)…"
}
/organizations/{org}/reports/{type}/{report_id}
One specific report.
/organizations/{org}/reports/{type}
Generate the report for the current period. Asynchronous, and it counts as a write. weekly only. An organization holds one report per period and type: generating again rebuilds that report in place, and it keeps its id.
A Google Ads strategy for the organization, written from its business profile, its performance, a live audit of its Google Ads account, keyword demand, and any documents you supply. The same document the Glitch app produces, held to the same playbook.
Writing one takes minutes, so generation is asynchronous: it answers with a task_tracking_id to poll and the document's id. Documents come back as markdown.
/organizations/{org}/strategy
Write a new strategy. Asynchronous. Needs reports:generate. Send documents to put your own material in front of it: each carries a name and content as markdown or plain text, at most five, 20,000 characters each.
Writing a strategy changes nothing else: no campaign is touched, and the organization's business profile is left as it stands.
// POST /v1/organizations/acme/strategy
{ "documents": [{ "name": "Q4 joint impact plan", "content": "# Plan\n\nGrow paid search 30%." }] }
// 202 Accepted
{
"status": "queued", "strategy": "str_18", "task_tracking_id": 90412,
"url": "https://app.glitchads.ai/reports/strategy?org=acme&strategy_run=18"
}
/organizations/{org}/strategy
List the organization's strategy documents, newest first. Bodies stay on the detail read.
/organizations/{org}/strategy/{strategy_id}
One document, by its id. While it is still being written markdown is null; if it failed, error says why.
// GET /v1/organizations/acme/strategy/str_18
{
"id": "str_18", "status": "succeeded", "title": "Q4 strategy",
"created_at": "2026-09-20T09:14:02Z", "finished_at": "2026-09-20T09:21:47Z",
"url": "https://app.glitchads.ai/reports/strategy?org=acme&strategy_run=18",
"markdown": "# Q4 strategy\n\nSpend where intent is…", "error": null
}
An organization can require a person to sign off every change made through a key or a connected app. An Owner turns it on under Settings, API Keys. It is off unless somebody turns it on.
While it is on, a mutating call does not execute: it answers 202 with approval_required and a queued_recommendation_id. Somebody approves or rejects it in Glitch, under Optimizations. Approving runs the change through exactly the path the direct call would have taken, attributed to whoever asked for it, and the guardrails run again at that moment, so a change the organization's budget cap no longer allows fails rather than going through. Reads and dry_run previews are never held, and never counted against the write quota.
A queued change that nobody acts on for seven days is closed as rejected with the reason expired: a week-old change describes an account that has since moved.
/queued-writes/{queued_id}
What became of a queued change. status is pending, approved, rejected or failed, and only pending ever changes. A person has to act, so poll slowly.
// POST /v1/organizations/acme/campaigns/spring-sale/negative-keywords
{ "keywords": ["free"], "dry_run": false }
// 202 Accepted
{
"error": {
"code": "approval_required",
"message": "This organization queues API writes for human sign-off. The change is pending as recommendation 'rec_812' in the dashboard's approval queue; poll /v1/queued-writes/rec_812 for the outcome.",
"doc_url": "https://glitchads.ai/docs/errors#approval_required",
"queued_recommendation_id": "rec_812"
}
}
// GET /v1/queued-writes/rec_812, once somebody has approved it
{
"id": "rec_812", "status": "approved", "operation": "add_negative_keywords",
"requested_at": "2026-09-20T09:14:02Z", "result": { "action_id": "act_4471", "added": 1 }
}
/organizations/{org}/campaigns/{slug}/detect
Run one detector. It never changes the campaign. For a campaign that runs in Google Ads, findings that can be acted on are saved as a pending recommendation, replacing the details of any pending recommendation of the same type on that campaign, and carry its recommendation_id. A draft's findings, and keyword_count findings, carry none. estimated_monthly_saving is left out when the detector does not compute one.
| Parameter | Type | Notes |
|---|---|---|
detector |
Typestring, required | One of high_cpc, no_conversion_keywords, zero_impression, negative_keywords, tcpa, exact_match, hipo, keyword_count, optimized_keywords, new_keywords. |
options |
Typeobject | negative_keywords: days, 1 to 90, default 7. high_cpc: threshold, how many times the campaign's average CPC, 1.0 to 10.0, default 3.0. keyword_count: threshold, 1 to 50, default 15. optimized_keywords: suggested_count, 1 to 50, default 15. tcpa: period_days, 7 to 90, default 30. zero_impression: include_paused, default false. The other four take none, and an option a detector does not take is a validation_error. |
curl -X POST https://api.glitchads.ai/v1/organizations/acme/campaigns/acme-prospecting/detect \
-H "Authorization: Bearer $GLITCH_API_KEY" -H "Content-Type: application/json" \
-d '{"detector":"negative_keywords","options":{"days":14}}'
// 200
{
"detector": "negative_keywords",
"findings": [ { "term": "free ads course", "match_type": "phrase", "cost_14d": 18.60, "clicks": 22,
"conversions": 0, "recommendation_id": "rec_4417" } ],
"estimated_monthly_saving": 41.20
}
/organizations/{org}/recommendations
What the optimisation engine and detector runs have proposed. Filter with campaign and status, which is pending by default and also takes applied or dismissed, meaning rejected by a person. A recommendation applied and then undone matches none of the three. estimated_monthly_saving is always null today.
// 200
{
"recommendations": [
{ "id": "rec_4417", "type": "negative_keywords", "campaign": "acme-prospecting",
"summary": "Exclude 'free ads course'", "estimated_monthly_saving": null,
"created_at": "2026-07-08T08:02:11Z" }
],
"total": 6, "next_cursor": null
}
/recommendations/{id}/apply
Apply one pending recommendation. dry_run defaults to true: the preview lists the operations it would run, or one summary operation when the recommendation does not itemise them. A recommendation applied in the last five minutes cannot be applied again, and a budget recommendation meets the same budget limits as PATCH. Needs recommendations:apply.
// 200 (dry run)
{ "dry_run": true,
"would_apply": [ { "op": "negative_keywords", "summary": "Exclude 'free ads course'" } ],
"estimated_monthly_saving": null }
// 200 (executed)
{ "applied": true, "action_id": "act_8841", "action_ids": [ "act_8841" ] }
/actions/{id}/undo
Reverse an executed action, whichever endpoint made it. Needs recommendations:apply. Undoing a state change starts the reverse transition in the background. restored lists what was reversed. Idempotent: undoing an action already undone returns an empty restored, spends no write quota and changes nothing.
// 200
{ "undone": true, "action_id": "act_8841",
"restored": [ "Reversed: Excluded 'free ads course' (phrase)" ] }
// 200 (already undone: nothing ran)
{ "undone": true, "action_id": "act_8841", "restored": [] }
/organizations/{org}/actions
The action log: every change, and what made it. Filter with from, to and source. Needs audit:read, which only an Owner's credential can carry.source says what kind of change it was: dashboard for a direct edit, including a PATCH, state change or negative keywords sent through this API, and engine for an applied recommendation, whoever applied it. api is accepted as a filter and matches nothing today. To find who made a change, read the actor block: type is api_key for a key, with prefix its environment prefix and last four characters and user its creator; user for a person, including one acting through an OAuth connection; and engine for Glitch itself.
// 200
{
"actions": [
{ "id": "act_8841", "at": "2026-07-08T09:14:02Z", "source": "engine",
"actor": { "type": "api_key", "prefix": "glads_live_3f2a", "user": "dave@glitchads.ai" },
"op": "add_negative_keyword", "campaign": "acme-prospecting",
"detail": "Excluded 'free ads course' (phrase)", "undone": false }
],
"total": 129, "next_cursor": "djE6MjA="
}
/organizations/{org}/audit/export
The access trail, for compliance. Owner only, answering role_denied to anyone else, and needs audit:read. Filter with from and to; without from the export covers the 30 days up to to, or up to now. format is json by default, a total and the events, and also takes csv with the same columns. Each event records the time, request id, operation, path, status code, error code, actor type, user, key prefix and last four characters, IP address and user agent. It includes failures, not only successful changes, and is not paginated.
curl "https://api.glitchads.ai/v1/organizations/acme/audit/export?from=2026-06-01&format=csv" \
-H "Authorization: Bearer $GLITCH_API_KEY" -o acme-audit.csv
/task-tracking/{id}
The state of any asynchronous job. Poll every 2 to 5 seconds until it is completed or failed; until then it is running. progress is 0 until the task completes, then 100. A completed task on a campaign, such as a campaign or ad creation, carries a result with the campaign slug and its url in Glitch, and a state change adds the state and synced_at. A report task carries none. A failed task carries an error with a reason, one of task_error, stuck_timeout, killed_by_restart, worker_shutdown, setup_required, google_ads_refused or google_ads_connection_broken, and a message saying what to do next. Three are to handle differently. setup_required: the job cannot run until something is connected, so it will fail the same way until somebody does that, and retrying is wasted. google_ads_refused: Google Ads declined the change and the message carries Google's own reason (a trial campaign whose status cannot change, for one); the campaign has to change in Google Ads first. google_ads_connection_broken: the job could not read Google Ads because the Google login behind the organization's account was revoked or deleted. It fails the same way until the account's owner reconnects Google Ads in Glitch, so retrying before that is wasted.
This one needs no scope of its own: any credential that can act in the task's organization can poll it, and any other id answers not_found.
// GET /v1/task-tracking/6102
{
"task_tracking_id": 6102, "type": "CAMPAIGN_STATE_UPDATE", "status": "completed",
"progress": 100,
"result": { "campaign": "acme-prospecting",
"url": "https://app.glitchads.ai/campaigns/google/acme-prospecting?org=acme",
"state": "paused", "synced_at": "2026-07-08T09:14:02Z" }
}
Keys are not administered through this API. They are created, rotated and revoked in the platform, under Account, API Keys: any member can create one, and rotation and revocation belong to the key's creator or an Owner.
What a key can then do, how it is formatted and how rotation and revocation behave are on the authentication page.
Find waste, look at the fix before taking it, take it, then change your mind.
# 1. find waste in the campaign
curl -s -X POST https://api.glitchads.ai/v1/organizations/acme/campaigns/acme-prospecting/detect \
-H "Authorization: Bearer $GLITCH_API_KEY" -H "Content-Type: application/json" \
-d '{"detector":"negative_keywords"}'
# 2. preview the recommendation it saved
curl -s -X POST https://api.glitchads.ai/v1/recommendations/rec_4417/apply \
-H "Authorization: Bearer $GLITCH_API_KEY" -H "Content-Type: application/json" \
-d '{"dry_run":true}'
# 3. apply it
curl -s -X POST https://api.glitchads.ai/v1/recommendations/rec_4417/apply \
-H "Authorization: Bearer $GLITCH_API_KEY" -H "Content-Type: application/json" \
-d '{"dry_run":false}'
# -> {"applied":true,"action_id":"act_8841"}
# 4. changed your mind
curl -s -X POST https://api.glitchads.ai/v1/actions/act_8841/undo \
-H "Authorization: Bearer $GLITCH_API_KEY"