> ## Documentation Index
> Fetch the complete documentation index at: https://docs.athenahq.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> Latest updates and changes to the AthenaHQ API

This page documents all notable changes to the AthenaHQ API, including new features, improvements, and bug fixes. We recommend reviewing this changelog regularly to stay informed about updates that may affect your integration.

## July 2026

### New: Track existing URLs as content with `POST /api/v1/content/track`

Publishing closed one half of the attribution loop; this closes the other. Until now the API could only attach a URL to content Athena generated (`publish`), so pages that already existed, guest posts, partner placements, or any page written outside Athena, could not be tracked at all. The new track endpoint (also the MCP tool `track_content_urls`) registers a list of up to 500 URLs as tracked Content Hub pages in one call:

* Each URL becomes an external content row whose normalized URL is the citation/metric join key, so citations and mentions in your monitored AI responses start attributing immediately. There is no publish step, no generation, and no credit cost. Third-party domains work; tracking is not limited to your own site.
* The call is idempotent per URL: a URL already present on the website (any content type) is never duplicated. The response splits `created` and `skipped`, and each skipped entry carries the existing row's `content_id` and `type`, so you can tell an already-tracked page apart from a collision with first-party generated content before chaining other calls onto the id. Tracking-param variants of the same page collapse to one row.
* Rows are created without page text, which citation tracking does not need. The page body can be pulled on demand from the Content Hub (Fetch content, on external pages without a body).
* Optional `sheet_id` files the new rows under a Content Hub tab; optional per-URL `title` overrides the slug-derived default.

### Changed: `get_tracked_content` no longer hides external and imported pages once their body is fetched

Tracked external and imported pages disappeared from `POST /api/v1/content` (and the MCP tool `get_tracked_content`) as soon as their page text was scraped, because the fill stamped an internal pipeline status that the listing's published filter excluded. The pages were still tracked, their citations still attributed, and the Content Hub still showed them; only this listing dropped them. External and imported pages are now always listed. Expect row counts to increase on websites with imported content: pages that were silently hidden reappear with their metrics intact. Generated content is unchanged and still appears only once published.

### New: Edit and delete brand facts

The Knowledge Base write surface is no longer append-only: an integration that adds facts can now also fix or remove them. Both endpoints require a website-admin API key and the Knowledge Base to be enabled for the organization.

* **PATCH /api/v1/knowledge-base/facts/{fact_id}** updates a fact's `text`, `source_url`, or `confidence`. Only provided fields change, and `source_url: ""` clears the stored source. Editing text re-embeds the fact for semantic search; a `409` is returned when the new text collides with a sibling fact captured from the same AI response (duplicates across other facts are left to Knowledge Base dedup). Review status never changes through this endpoint. Also available as the MCP tool `update_brand_fact` on API-key connections and org-bound OAuth sessions with brand-knowledge write permission.
* **POST /api/v1/knowledge-base/facts/delete** permanently deletes 1-50 facts in one call (a single delete is a one-element array). There is no undo. Ids with no fact on the website are reported in `not_found_ids` instead of failing the call, so retries are idempotent. Deleted facts drop out of semantic search and the owning pillar's document regenerates without them. Also available as the MCP tool `delete_brand_facts`; on OAuth sessions it requires the signed-in user to hold the admin role, matching the dashboard's delete gate.

### New: website objects carry the partner `external_id` everywhere

For partner integrations, every endpoint that returns website objects now includes the partner-supplied identifier mapped to that website, so you can match sites by your own id instead of by URL (a site's `websiteUrl` changes when its primary URL is edited in Athena):

* `GET /api/v1/groups` and `GET /api/v1/groups/{group_id}`: each website object inside `websites` carries `externalId` (`null` when no mapping exists), alongside the group-level `externalId`. The `PATCH /api/v1/groups/{group_id}` response has the same shape. These per-website fields have been returned since early July and are now documented.
* `GET /api/v1/users/{user_id}/websites`: each item now includes `external_id` (snake\_case, matching that endpoint's field naming).

The field appears only on responses to partner API keys; direct-customer responses omit it entirely. Mappings are created by passing `external_id` to `POST /api/v1/websites` or `POST /api/v1/websites/provision`; `GET /api/v1/websites` and `GET /api/v1/websites/by-external-id/{external_id}` already exposed them.

### Changed: fact listings exclude Oracle-generated facts by default (new `include_oracle` parameter)

`GET /api/v1/knowledge-base/facts` (and the MCP tool `get_brand_facts`) now excludes facts produced by Oracle's automated analysis from every listing, including the default one. Those facts are never routed to pillars and on most websites dominated the corpus (often millions of rows, inserted continuously), burying the user-curated facts the endpoint exists to surface. The response shape is unchanged; only which rows match changed. To include Oracle-generated facts, pass the new `include_oracle=true` parameter; it composes with every other filter, so `include_oracle=true&unfiled=true` reproduces the previous unfiled behavior. `GET /api/v1/knowledge-base/facts/search` is unaffected and still searches the full approved corpus, so a fact returned by search (with `pillar: null`) is not guaranteed to appear in a default listing.

### New: Mark content as published with `POST /api/v1/content/{content_id}/publish`

Until now the content workflow ended one step short over the API: you could create a piece, revise it, approve its brief, and publish it to your site yourself, but there was no way to tell Athena the piece was live. The item stayed unpublished forever, and since the URL is the join key citations and mentions attach to, citation tracking never started.

The new publish endpoint (also the MCP tool `publish_content`) closes that loop. Call it after the piece is actually live, passing the exact public URL of the page:

* The URL is stored as the item's citation/metric join key (protocol and trailing slashes are stripped; a bare `acme.com/blog/post` is fine). Query strings and fragments are excluded from the join key for non-YouTube URLs, so pasting a UTM-tagged URL does not break attribution; the display URL keeps them. YouTube URLs keep their params, since the video id lives there.
* The publish time is stamped and the pipeline stage moves to `done`, so the item now counts as published in Content Hub and in `get_tracked_content`.
* A first publish requires a finished article (stage `generated`). Briefs, still-generating or failed pieces, and items with a scheduled CMS publish return `409` with the reason, so incomplete content cannot be marked live.
* Calling it again on an already-published item updates the tracked URL only, keeping the original publish time. The response's `already_published` flag tells you which of the two happened. External and imported pages are live by definition, so a call on them is always a URL-only update.

This is the only way to set a content URL over the API: the rename endpoint (`PATCH /api/v1/content/{content_id}`) remains deliberately title-only.

### New: `list_content` MCP tool for enumerating unpublished drafts

`get_tracked_content` lists content with citation and impression metrics only once its generation pipeline is finished, so a sheet full of in-progress drafts listed as empty: there was no way to enumerate a sheet's unpublished pipeline items without already holding their `content_id`s. Rather than blur that tool's "tracked" semantics with a flag, there is now a dedicated MCP tool, `list_content`, that enumerates every content item a website has, published or not: in-flight drafts, briefs, snipes, optimize and slice runs, scheduled and failed items, plus tracked pages.

It returns identity fields only (`content_id`, `title`, `type`, `stage`, `sheet_id`, `sheet_name`, `topic_name`, `url`, timestamps), no metrics, and supports `sheet_id`, `content_type`, and `stage` filters with the usual `page_num` / `page_size` pagination. Passing the `sheet_id` of a view-type tab (see `sheet_type` on `get_content_hub_sheets`) lists the shared main pool that views overlay, without the view's saved filters. `stage` is the raw pipeline workflow state, not a publication flag: `null` for pages with no pipeline record (most external and imported tracked pages), otherwise the stage the pipeline is at (`pending_brief`, `generated`, `done`, `scheduled`, `failed`, and so on). An item counts as published in Content Hub terms when its stage is `done` (or it is an external or imported page) and it has a `url`; manual editor items carry `generated`. The listing reads from the primary store and is uncached, so an item is listable the moment `create_content` returns its `content_id`, before any brief approval, generation, or publish. `get_tracked_content` and `POST /api/v1/content` are unchanged.

### New: Deterministic draft edits with `POST /api/v1/content/{content_id}/edit`

Until now the only way to change an article body over the API was `revise`, which hands your instruction to an AI model and saves whatever it writes. The new edit endpoint (also the MCP tool `edit_content`) is the deterministic counterpart: you send exact find-and-replace pairs and your replacement text lands verbatim, no model involved.

* Each edit's `find` text must match the current body exactly once. Zero matches (stale snapshot, wrong anchor) or multiple matches (ambiguous anchor) fail the whole call with `400` and nothing is saved; extend the `find` text until it is unique.
* Up to 20 edits per call, applied in order, with later edits seeing earlier results. The batch is recorded as one new version, restorable like any other.
* Optional `expected_version_number` (from the draft endpoint) makes the call fail with `409` when a new version was recorded after your read (a revision, edit, or restore). An app autosave changes the body without recording a version; that drift is caught by the anchors themselves, since a moved or vanished `find` text fails the call.

The `revise` and `brief/revise` descriptions now also state explicitly that their rewrites are AI-generated text, not your literal wording, so it is clear which of the two tools to reach for.

### Fixed: Narrow revise instructions could replace the whole article with the edited passage

`POST /api/v1/content/{content_id}/revise` (and the MCP tool `revise_content`) could destroy a draft when given a narrowly-scoped instruction such as "change only this sentence, alter nothing else": the model sometimes returned only the edited passage, and that fragment was saved as the new article. The endpoint now guards against this. A revision that comes back dramatically shorter than the current draft, without an instruction that calls for shortening, is retried with an explicit whole-document directive; if it still comes back as a fragment, the call fails with a `500` and the draft is left unchanged. Nothing is ever silently truncated, and earlier text remains restorable via the version endpoints.

The response now also includes `body_word_count` and `previous_body_word_count` (both integers), so callers can verify the size change themselves. The returned `body` is always the complete replacement article. Same additions apply to `POST /api/v1/content/{content_id}/brief/revise`'s underlying guard, though its response shape is unchanged.

### Changed: `filters.competitor_ids` on content endpoints is now strict

`POST /api/v1/content` and `POST /api/v1/content/prompts` accept `filters.competitor_ids`. Previously the filter also kept responses that mention no competitors at all, so filtered citation rates and response counts included competitor-free responses. The filter is now strict: a response counts only when it mentions at least one of the given competitor ids, matching how the same filter behaves on the responses and sources endpoints. Requests that omit the filter are unchanged. Both endpoints are also significantly faster.

### New: `fact_review` on content generation

`POST /api/v1/content/generate` (and the MCP tool `create_content`) now accepts an optional `fact_review` object for draft mode: `{ topic_ids, kept_claim_ids, excluded_claim_ids }`. When present, generation uses exactly the reviewed knowledge-base pillars and kept facts instead of computing a selection of its own, so what a reviewer approved is what the draft is written from. `pinned_claim_ids` no longer influences selection when a review is present (the review already reflects any pins); it is still recorded for display. Requires the knowledge base to be enabled: sending `fact_review` with `kb_mode: "none"` or a non-draft `mode` returns a validation error.

Content generation is also now strictly pillar-scoped: knowledge-base claims without a pillar are never used, whether selected automatically, pinned via `pinned_claim_ids`, or kept via `fact_review`. Ids that resolve to an unfiled claim are dropped and reported in the run's selection outcome. File a fact under a pillar to make it available to generation.

### New: `409` when deleting a website that holds your subscription

`POST /api/v1/websites/{website_id}/delete` now returns `409 Conflict` instead of silently succeeding when the target website carries your organization's subscription.

Plans attach per organization, but subscriptions bought before that change sit on one specific website. Deleting that website used to soft-delete the record while leaving the subscription and its credit balance stranded on it: billing continued, and every remaining website reported zero credits. The endpoint now refuses the delete and returns the reason, so the plan can be cancelled or moved first.

Deletes of websites that do not hold a subscription are unaffected, as are all `restore` calls. If you delete websites programmatically, handle `409` as "cancel or move billing first", not as a retryable error.

### Fixed: `cited` was always `false` on responses

`POST /api/v1/responses` and `GET /api/v1/responses/{response_id}` returned `cited: false` on every response, even when the response's own `sources[]` contained your website's domain. The flag was read from a stored value that the ingestion pipeline never populated. It is now derived from the response's sources at read time: `cited` is `true` when any source resolves to your website's root domain (protocol, `www.`, and letter-case insensitive). The fix applies to all historical responses, not just new ones, and the response shape is unchanged. Aggregate citation metrics (such as the sources endpoints) were computed independently and were always correct.

### Fixed: Content detail returned null text for brief-stage drafts

`GET /api/v1/content/{content_id}` (and the MCP tool `get_content_detail`) returned `status`, `brief`, `body`, and every other generation field as `null` for any draft whose article had not been written yet, even though the brief existed and was visible in the dashboard. The bug hid exactly the pieces most worth reading over the API: drafts parked at `generated_brief` awaiting brief approval. These now return their brief and status correctly. A genuinely unknown or inaccessible `content_id` continues to return `404`; `null` fields now only mean the field is actually empty.

### New: `stage` on the content status endpoint

`GET /api/v1/content/{content_id}/status` (and the MCP tool `get_content_status`) now returns a `stage` field alongside the normalized `status`. `status` is unchanged (`running | succeeded | failed`), but it cannot distinguish "actively generating" from "brief ready, waiting for approval": both report `running`, and a draft created with `auto_approve: false` waits at the brief stage indefinitely. `stage` exposes the raw pipeline stage (`scheduled`, `pending`, `pending_brief`, `generated_brief`, `pending_article`, `generated`, `done`, `failed`, or `null` when generation has not started), so a poller seeing `stage: "generated_brief"` knows to call `POST /api/v1/content/{content_id}/brief/approve` rather than keep waiting.

### New: Review and iterate on briefs and drafts

Content generation used to be one-shot: you could start a piece and poll it, but changing what came back meant generating a new one and paying for it again. You can now read a draft, revise it with a plain-language instruction, and step back through its history — for the brief as well as the article.

Every revision is recorded as a version, so nothing is overwritten irrecoverably. The brief and the article share one timeline: `get_content_versions` tells the whole story of a piece, from the brief it was planned with to the article it became.

**The brief phase.** Pass `auto_approve: false` to `POST /api/v1/content/generate` and the piece stops at `generated_brief` instead of writing the article. That gate already existed; these endpoints make it usable end to end.

* **GET /api/v1/content/{content_id}/draft** — The current text: article body, brief, meta description, status, and the latest version number. A narrow read for when you only want the text — `GET /api/v1/content/{content_id}` remains the full record. Also available as the MCP tool `get_content_draft`.
* **POST /api/v1/content/{content_id}/brief/revise** — Rewrites the brief from an instruction ("add a section on pricing objections"). Also `revise_brief`.
* **POST /api/v1/content/{content_id}/brief/approve** — Approves the brief and starts the article from it, as the brief currently stands. Consumes content credits. Also `approve_brief`.

**The article phase.**

* **POST /api/v1/content/{content_id}/revise** — Rewrites the article from an instruction. Iterate by calling it again; each call starts from the current text, so there is no version to pass. Also `revise_content`.
* **POST /api/v1/content/{content_id}/versions** — The version history, newest first, with each entry's label, how it was produced, and who made it. POST rather than GET because it takes pagination in the body. Also `get_content_versions`.
* **GET /api/v1/content/{content_id}/versions/{version_id}** — The full text of one version. Fetch two and diff them yourself to see what a pass changed. Also `get_content_version`.
* **POST /api/v1/content/{content_id}/versions/{version_id}/restore** — Brings an earlier version's text back as the current draft. Also `restore_content_version`.

Three things worth knowing:

* **Restoring never deletes.** The restored text is appended as a new version on top of the history, so the passes in between stay readable and restorable, and the history records that a restore happened.
* **Revising is not regenerating.** A revision edits the text you already have; it does not re-run research or scraping. Revising a brief after the article exists corrects the brief for a future regeneration — it does not rewrite the article.
* **A piece being generated cannot be revised.** While a generation is in flight these endpoints return `409`; poll `GET /api/v1/content/{content_id}/status` until it reports `succeeded`.

### New: Create saved views over MCP

You can now create saved views (dashboard filter presets) through the MCP server. A saved view captures a named set of filters and appears in the dashboard filter bar for every member of the website.

* **`create_saved_view`** (MCP tool) — Creates a saved view from a `name`, an optional `icon`, and a `filters` object. The `filters` payload is stored verbatim (keys are not case-mapped) and is capped at 64KB. There is deliberately no REST equivalent: creating filter presets is an assistant workflow, not a partner-integration one.

Views created on an API-key MCP connection have no owning user: `user_id` is `null` in `GET /api/v1/saved-views` responses (previously it was always a user ID) and the dashboard shows them as created via API. Such views can only be edited or deleted by website admins, in the dashboard. Views created over a sign-in MCP connection are attributed to the connected user, same as views created in the dashboard.

### New: AthenaHQ plugin for ChatGPT

AthenaHQ is now available as an official plugin in ChatGPT, so connecting no longer requires a custom MCP connector or developer mode. [Install the plugin](https://chatgpt.com/plugins/plugin_asdk_app_6a4da6c4bcfc81919397faceada2af69), sign in with your AthenaHQ account, and mention `@AthenaHQ` in any conversation to ask about share of voice, citations, sources, tracked prompts, and more. Plugin connections are read-only, same as other sign-in connections. See [MCP Server](/api-reference/mcp) for details.

### New: Attributes (brand-perception keywords)

You can now retrieve the brand-perception attributes Athena extracts from AI model responses — keywords like "Affordable" or "Slow Support" — and how often each is mentioned. This is the qualitative half of the picture: the existing metrics tell you how often and where your brand appears, attributes tell you what models actually say about it.

* **GET /api/v1/attributes** — Lists the attributes tracked for a website (`{ "id", "name", "positive" }`). Use it to discover attribute IDs. Also available as the MCP tool `get_attributes`.
* **POST /api/v1/attributes/cumulative** — Per attribute, how many responses mentioned it across the date range, and the percentage. Attributes nobody mentioned are returned with `response_count` 0 rather than omitted. Also available as the MCP tool `get_attribute_metrics`.
* **POST /api/v1/attributes/competitors** — The same breakdown per tracked competitor, so you can compare how your brand is described against theirs. Each competitor has its own denominator; competitors with no responses in the date range are omitted. Also available as the MCP tool `get_competitor_attribute_metrics`.
* **POST /api/v1/attributes/time-series** — The daily trend for one attribute, brand and competitors side by side. Requires an `attribute_id` from the list endpoint. Days with no data are returned as zeros, so the series is gap-free.

Two things worth knowing when reading this data:

* `positive` describes the **keyword itself**, not an individual mention of it. "Slow Support" is always `positive: false`, however a given response used it.
* `percentage` is the share of **analyzed** responses, not of all responses. Athena samples responses for attribute extraction, and un-analyzed ones are excluded from the denominator.

### New: Knowledge Base writes

You can now feed a website's brand Knowledge Base through the API. Write inputs are declarative — fact text, optional source URL, optional pillar — with no status or approval field: every fact runs the full ingestion pipeline (deduplication, approval gates, pillar routing) and the response reports what the pipeline decided, per fact. The `outcome` values (`approved`, `pending`, `duplicate`) form an extensible enum: new values may be added as the pipeline evolves, so treat unknown values as non-terminal. Both endpoints require a website-admin API key and the Knowledge Base to be enabled for the organization.

* **POST /api/v1/knowledge-base/facts** — Adds 1-50 brand facts in one call and returns one outcome per fact (`{ "index", "outcome", "fact_id", "pillar_id" }`), which doubles as the ingest report. Facts pinned to a `pillar_id` land exactly there; the rest are routed automatically and may land unfiled. Also available as the MCP tool `add_brand_facts` on API-key connections and org-bound OAuth sessions with brand-knowledge write permission.
* **POST /api/v1/knowledge-base/pillars** — Creates a pillar from a `name` and optional `description`. Get-or-create semantics: an existing name returns the pillar with `created: false`, keeping retries idempotent. Also available as the MCP tool `create_pillar`.

### New: Knowledge Base reads

You can now read a website's brand Knowledge Base through the API: the approved brand facts (statements like "Our Pro plan starts at \$99/month", each backed by a source) and the pillars that organize them. All four endpoints require the Knowledge Base to be enabled for the organization; when it isn't, they return a `403` with `"Knowledge Base is not enabled for this organization"`.

* **GET /api/v1/knowledge-base/facts/search** — Semantic search over a website's approved brand facts. Combines vector similarity with full-text search, so it also reaches facts not filed under any pillar. Also available as the MCP tool `search_brand_facts`.
* **GET /api/v1/knowledge-base/facts** — Lists brand facts, newest first, with offset paging and filters: pillar, review status (defaults to approved), source type, and `unfiled=true` for facts not under any published pillar. Also available as the MCP tool `get_brand_facts`.
* **GET /api/v1/knowledge-base/pillars** — Lists the website's pillars (excluding archived ones) with approved-fact counts, whether a synthesized document exists, and when each was last researched. Also available as the MCP tool `get_pillars`.
* **GET /api/v1/knowledge-base/pillars/{pillar_id}/document** — Fetches a pillar's synthesized markdown document; returns `document: null` when the pillar exists but has no document yet. Also available as the MCP tool `get_pillar_document`.

### New: Persona discovery

You can now discover a website's personas through the API. Persona IDs previously appeared as bare `persona_id` values in other responses with no way to resolve them to names.

* New **GET /api/v1/personas** endpoint lists a website's personas with per-persona assigned-prompt counts (`{ "id", "name", "description", "prompt_count" }`). Also available as the MCP tool `get_personas`.

### New: Content generation endpoints and MCP actions

You can now create and manage content in Athena programmatically, closing the loop from insight to action: spot a gap, then draft content for it without leaving the API or your AI assistant.

* **POST /api/v1/content/generate** — Starts the content pipeline in one of four modes: `draft` (write new content from `prompt_ids`), `snipe` (outrank a competitor URL), `optimize` (improve an existing URL for AI search), or `slice` (split one URL into several articles). Returns durable `content_ids` you can poll. Consumes content credits. Requires a website-admin API key. Also available as the MCP tool `create_content` on API-key connections.
* **GET /api/v1/content/{content_id}/status** — Returns the normalized generation status (`running`, `succeeded`, or `failed`) for one content item. Poll it per `content_id` returned by the generate endpoint. Also available as the MCP tool `get_content_status`.
* **PATCH /api/v1/content/{content_id}** — Renames a content item's title. The URL is the item's stable identity and cannot be changed. Requires a website-admin API key. Also available as the MCP tool `rename_content` on API-key connections (initially shipped as `update_content`; renamed because the tool only renames the title).
* **DELETE /api/v1/content/{content_id}** — Permanently deletes a content item (no undo). Requires a website-admin API key. Also available as the MCP tool `delete_content` on API-key connections.

Listing and reading content were already available (**POST /api/v1/content**, **GET /api/v1/content/{content_id}**, and the MCP tools `get_tracked_content` / `get_content_detail`).

### New: Topic management endpoints

You can now manage topics via the API, completing the programmatic prompt management round trip: create a topic, get back its `id`, and pass it as `prompts[].topic_id` when creating prompts.

* **GET /api/v1/topics** — Lists a website's active topics with the count of active prompts in each. Use it to resolve a topic name to its `id`. Also available as the MCP tool `get_topics`.
* **POST /api/v1/topics** — Creates a topic on a website from a `name` and optional `description`. Get-or-create semantics: if an active topic with the same name already exists, the existing topic is returned with `created: false` instead of failing, so retries and re-imports are idempotent. Requires a website-admin API key. Also available as the MCP tool `create_topic` on API-key connections.
* **PATCH /api/v1/topics/{topic_id}** — Updates a topic's `name` and/or `description`. Renames don't affect metrics or prompt grouping (everything joins on the topic's `id`). Requires a website-admin API key. Also available as the MCP tool `update_topic` on API-key connections.
* **DELETE /api/v1/topics/{topic_id}** — Soft-deletes a topic. By default its prompts are kept (they stay active and can be re-categorized); pass `delete_prompts=true` to also soft-delete every active prompt in the topic. Reversible: creating a topic with the same name restores it. Requires a website-admin API key. Also available as the MCP tool `delete_topic` on API-key connections.

### New: Prompt tag discovery

You can now discover a website's prompt tags through the API. Previously the `prompt_tags` filter on **GET /api/v1/prompts** and the `tag_ids` field on **POST /api/v1/prompts** required tag UUIDs that the API did not expose anywhere.

* **GET /api/v1/prompts** now includes a `tags` array on each prompt (`[{ "id", "name" }]`, sorted by name, empty when untagged). Existing fields are unchanged.
* New **GET /api/v1/prompt-tags** endpoint lists a website's tags with per-tag prompt counts (`{ "id", "name", "prompt_count" }`), so a tag picker can populate without fetching all prompts. Also available as the MCP tool `get_prompt_tags`.

### Changed: Provisioning responses report billing entity creation

Website and group creation responses now include `billingEntityCreated`: whether the billing entity that backs credits was provisioned. When `false`, the website or group was created but cannot hold credits yet: setting credits fails with a `400` until it is repaired (for groups, call **PATCH /api/v1/groups/{group_id}** with `billing_enabled: true`; for websites, contact AthenaHQ support). Previously a failed billing setup was silent and only surfaced when setting credits later.

Affects **POST /api/v1/websites** and **POST /api/v1/websites/provision** (always present), and **POST /api/v1/groups** (present when `billing_enabled` is requested).

### New: Write tools on the MCP server

The MCP server is no longer read-only. Connections authenticated with an **API key** now get write tools mirroring the corresponding REST endpoints 1:1: same field names (snake\_case), same validation, same audit logging. OAuth-connected MCP sessions remain read-only and do not see these tools.

* `create_prompts`, `update_prompt`, `set_prompts_status`, `delete_prompt`: the prompt management operations (POST/PATCH/DELETE `/api/v1/prompts...`).
* `create_location`, `update_location`, `delete_location`: the location management operations (POST/PATCH/DELETE `/api/v1/locations...`).
* `start_response_streaming`: starts a response-streaming run (POST `/api/v1/response-streaming/start`). Status polling was already available as `get_response_streaming_status`.

Read endpoints that previously had no MCP tool are now exposed too, completing 1:1 coverage of the procedure-backed API surface: `get_user_by_email` ([POST /api/v1/users/by-email](/api-reference/team-management/look-up-user-by-email), global API key required), `get_response_detail` (GET /api/v1/responses/{response_id}), `get_groups` and `get_group_detail` (GET /api/v1/groups..., global API key required), `check_ai_access` (POST /api/v1/ai-access/check), and `get_position_distribution` (POST /api/v1/metrics/position/distribution).

### New: Role management endpoints

You can now manage roles via the API — list the built-in system roles (Admin, Editor, Viewer, Billing) and your organization's custom roles, and create, update, or delete custom roles with per-category permission matrices. Creating custom roles is available on the Enterprise plan. All role endpoints require a **global API key**.

* **GET /api/v1/roles** — List all roles with their resolved permission matrices.
* **POST /api/v1/roles** — Create a custom role (Enterprise plan).
* **GET /api/v1/roles/{role_id}** — Fetch a single role.
* **PATCH /api/v1/roles/{role_id}** — Update a custom role's permissions.
* **DELETE /api/v1/roles/{role_id}** — Delete a custom role (fails if still assigned).

### Changed: Assign any role with `role_id`

Invites and organization member role updates now accept a `role_id` — a system role or one of your organization's custom roles — in addition to the coarse `role` (`admin`/`viewer`), which keeps working unchanged. `role` and `role_id` are mutually exclusive.

Affects **POST /api/v1/invites**, **POST /api/v1/invites/bulk**, and **PATCH /api/v1/organizations/members/{user_id}**.

### Changed: Mention rate and share of voice entries include relative mention rate

The mention rate and share of voice endpoints now return a `relative_mention_rate` field for every brand/competitor entry: the entity's mentions as a percentage (0-100) of responses that mention at least one tracked brand. This is the same "Relative" rate shown in the dashboard's mention rate card. The existing `mention_rate` field stays absolute (share of all responses), and all existing fields are unchanged.

Affects **[POST /api/v1/metrics/mention-rate/cumulative](/api-reference/metrics/cumulative-mention-rate)**, **[POST /api/v1/metrics/mention-rate/time-series](/api-reference/metrics/mention-rate-over-time)**, **[POST /api/v1/metrics/share-of-voice/cumulative](/api-reference/metrics/cumulative-share-of-voice)**, and **[POST /api/v1/metrics/share-of-voice/time-series](/api-reference/metrics/share-of-voice-over-time)** (per entry, per day on the time-series variants). Also returned by the MCP tools `get_mention_rate_cumulative`, `get_mention_rate_time_series`, `get_share_of_voice_cumulative`, and `get_share_of_voice_time_series`.

### New: Trigger response streaming via the API

You can now start a response-streaming run (query the AI models for a website's prompts and ingest the answers) and poll its progress via the API — the same run the dashboard's stream dialog kicks off.

* **[POST /api/v1/response-streaming/start](/api-reference/basics/start-response-streaming)** — Start a run for all active prompts or a selection (up to 1,000), with optional model selection, prompt variations, personas, and locations. Returns the `workflow_id` and `status: "running"`. Requires a website-admin API key.
* **[GET /api/v1/response-streaming/status](/api-reference/basics/get-response-streaming-status)** — Read the latest run's state (`idle` / `running` / `completed` / `failed` / `cancelled`) and queue progress (`pending`, `processing`, `completed`, `failed`, `total`, `percentage`). Pass `workflow_id` to read a specific run.

### New: Look up a user by email

You can now resolve a user's `user_id` from their email address, scoped to your organization. The email is sent in the request body so it never lands in logs.

* **[POST /api/v1/users/by-email](/api-reference/team-management/look-up-user-by-email)** — Returns the user's `user_id`, name, and `access_type` (`organization` or `website`) when the email belongs to your organization; `404` otherwise (indistinguishable from an unknown email, so it cannot be used to enumerate accounts). Requires a global API key.

### New: Remove a user from websites

You can now batch-remove a user's explicit website memberships in one call — the companion to listing a user's websites.

* **[DELETE /api/v1/users/{user_id}/websites](/api-reference/team-management/remove-user-from-websites)** — Remove the user from the websites named in the `website_ids` query parameter (comma-separated, up to 100). Idempotent: websites where the user has no explicit membership return status `not_a_member` instead of failing. Requires a global API key.

### Changed: Filter prompts by tag

[GET /api/v1/prompts](/api-reference/basics/get-prompts) now accepts an optional `prompt_tags` filter (one or more prompt tag IDs) plus a `prompt_tags_operator` (`is_any_of` (default) or `has_all_of`) to control how multiple tags combine. All tag IDs must belong to the same website as `website_id`. Also available on the MCP `get_prompts` tool.

### New: Read a single content item

You can now fetch the full detail of one tracked content item — including its actual text: the content brief (`brief`) and the article/page body (`body`, which holds generated drafts, optimize rewrites, snipe articles, authored text, and scraped tracked-page bodies). The response also carries `status`, so you can poll it while generation runs — `generated_brief` means the brief is ready while the article is still being written.

* **[GET /api/v1/content/{content_id}](/api-reference/content/get-content-detail)** — Full detail for one content item, including `brief`, `body`, cited source URLs, links, and `status`. Also available as the MCP tool `get_content_detail`.

### Changed: `manual` content type filter

The tracked-content endpoint's `content_type` filter now accepts **`manual`** — user-authored content created directly in the editor (the "Blank Editor" flow), as opposed to Athena-generated or imported content. Existing values are unchanged.

Affects **POST /api/v1/content** (`content_type` filter).

## June 2026

### Changed: Member `role` is now the role's slug

Member-list endpoints now return each member's `role` as its **slug** — the role's normalized name (lowercase, no spaces) — rather than only `admin`/`viewer`. Built-in roles are `admin`, `viewer`, `editor`, and `billing`; a custom role uses its own id (e.g. `contentmanager`). `admin` and `viewer` are unchanged, so integrations that check for them keep working; members that previously reported `viewer` because of a non-admin role (Editor, Billing, custom) now surface their real role.

Affects **GET /api/v1/organizations/members**, **GET /api/v1/websites/{website_id}/members**, **GET /api/v1/groups/{group_id}/members**, and **GET /api/v1/users/{user_id}/websites**.

### New: Manage prompts via the API

Prompts are no longer read-only. You can now create, update, pause/unpause, and delete prompts via the API — the same operations the dashboard performs, with the same geography and ownership validation.

* **[POST /api/v1/prompts](/api-reference/basics/create-prompts)** — Create one or more prompts (up to 1,000 per request), with optional topic, geography, persona/location/tag associations, and paused state.
* **[PATCH /api/v1/prompts/{prompt_id}](/api-reference/basics/update-prompt)** — Update a prompt's text, type, volume, topic, geography, or locations. Returns a before/after snapshot.
* **[PATCH /api/v1/prompts/status](/api-reference/basics/pause-or-unpause-prompts)** — Pause or unpause multiple prompts in one transaction.
* **[DELETE /api/v1/prompts/{prompt_id}](/api-reference/basics/delete-prompt)** — Delete a prompt (soft when it has responses, hard otherwise).

### New: Manage locations via the API

You can now create, update, and delete locations via the API. `country` is accepted case-insensitively and stored as its canonical label, and any attached prompts are validated against the location's country.

* **[POST /api/v1/locations](/api-reference/basics/create-location)** — Create a location with a country and optional prompt associations.
* **[PATCH /api/v1/locations/{location_id}](/api-reference/basics/update-location)** — Update a location's name, country, and/or prompt associations. Returns a before/after snapshot.
* **[DELETE /api/v1/locations/{location_id}](/api-reference/basics/delete-location)** — Soft-delete a location.

## May 2026

### New: MCP server for Claude, ChatGPT, and other AI assistants

You can now connect AthenaHQ to Claude, ChatGPT, and other AI assistants over the [Model Context Protocol](/api-reference/mcp). The read-only server exposes your metrics, prompts, responses, content, sources, competitors, and pitches as tools, so an assistant can answer questions about your AI search visibility in plain language.

* Connect **Claude.ai** or **ChatGPT** by adding a custom connector and signing in with your AthenaHQ account (no API key).
* Connect other MCP clients (including **Claude Code**) with an API key via the `x-api-key` header.

Server URL: `https://api.athenahq.ai/api/mcp`.

### New: Sources endpoints

You can now pull cited-sources data via the API (and via MCP — these are the `get_sources` / `get_source_pages` tools). Each row is classified as `owned` / `competitor` / `partner` / `third_party` based on your tracked competitors, configured partners (affiliates), and per-website source-type overrides.

* **[POST /api/v1/sources](/api-reference/sources/list-sources-by-root-domain)** — Domain-grouped sources for a website. Top cited root domains with citation, mention, brand-mention, and impression metrics for the date range. Supports filters, search, sort, and pagination.
* **[POST /api/v1/source-pages](/api-reference/sources/list-source-pages-per-url)** — Per-URL companion to the above. Returns one row per cited `normalized_url` with the same metrics plus a per-URL daily citation sparkline (`daily_mentions`).

### New: Pitch Workspace endpoints

You can now read pitch workspace reports via the API. Pitches are organization-level resources (not scoped to a single website), so these endpoints require a **global API key** — website-scoped keys are rejected with `403 Forbidden`.

* **[GET /api/v1/pitches](/api-reference/pitch-workspace/list-pitches)** — List non-deleted pitches in the calling organization, with company metadata and run status.
* **[GET /api/v1/pitches/{pitch_id}](/api-reference/pitch-workspace/get-pitch-report)** — Fetch a single pitch report: tracked competitors with mention/sentiment stats, generated prompts, positive/negative attributes for both the target and its competitors, top citing domains, and aggregate metrics (`total_responses`, `brand_mentions`, `sentiment_score`, `response_rate`).

### New: Content Hub endpoints

You can now read Content Hub data — tracked URLs, citation/impression metrics, and per-URL prompt breakdowns — via the API. These power GEO reporting that includes both 1st-party (Athena-created) and 3rd-party (link-building placements, Reddit, partner posts) content in one place.

* **[GET /api/v1/content/sheets](/api-reference/content/list-content-hub-sheets)** — List the Content Hub tabs configured for a website. Call this first to discover the `sheet_id`s you can filter by.
* **[POST /api/v1/content](/api-reference/content/list-tracked-content)** — Paginated tracked content with citations, citation %, estimated impressions, and total responses for the date range. Filter by `sheet_id`, `content_type`, `mentioned`, and the standard filter knobs.
* **[POST /api/v1/content/prompts](/api-reference/content/per-content-prompt-citation-breakdown)** — For a single content item, the list of prompts whose AI responses cite it — with per-prompt citation counts and percentages. Works for both topic-attached and 3rd-party content without a topic assignment.

### New: Saved views endpoint

You can now fetch saved views (filter presets) for a website via the API. Saved views capture a named set of dashboard filters that can be reused across the product.

* **[GET /api/v1/saved-views](/api-reference/basics/get-saved-views)** — List saved views for a website. Returns each view's filters, icon, and creator metadata.

### New: Group saved views endpoint

You can now fetch group-level saved views via the API. Group saved views capture filter state shared across the websites in a group rather than scoped to a single website.

* **[GET /api/v1/group-saved-views](/api-reference/basics/get-group-saved-views)** — List saved views for a group. Pass `group_id`; authorization runs against the group's org.

## April 2026

### Updated: Multi-country support on prompts

[GET /api/v1/prompts](/api-reference/basics/get-prompts) now returns a `countries` array on each prompt, listing every country targeted by that prompt along with an `is_primary` flag. The primary country is listed first; remaining countries follow in alphabetical order.

* `countries: [{ country: string, is_primary: boolean }]` — New field. Empty array when the prompt has no countries assigned.
* `country: string` — Deprecated, but still populated for backward compatibility. Returns the primary country (or the alphabetically first, or `"United States"` as a final fallback). New integrations should read `countries` instead.

### New: Delete and restore websites

You can now delete and restore websites via the API. Deleting removes a website from your organization and stops all associated processing; restoring brings it back exactly as it was, with all historical data preserved. This is the recommended flow for partner integrations that need to handle customer churn and reactivation.

* `POST /api/v1/websites/{website_id}/delete` — Deletes a website.
* `POST /api/v1/websites/{website_id}/restore` — Reactivates a deleted website.

Both endpoints require a global API key. Both are idempotent: re-calling them on a website that is already in the target state returns 200.

### Updated: Invite users to groups

[POST /api/v1/invites](/api-reference/team-management/create-invite) now accepts `type: "group"` with a `group_id`, granting the user access to every website in the group. [POST /api/v1/invites/bulk](/api-reference/team-management/bulk-invite-to-websites-or-groups) accepts a new `group_ids` array (mutually exclusive with `website_ids`, max 50) for inviting a user to multiple groups in one request.

Both branches behave consistently with the existing org/website invites: `invite_method: "direct"` adds the user immediately (creating a new user account automatically if the email has none) without sending any email, while `invite_method: "email"` creates a pending invite and sends an invitation. Group invites require a **global API key**. Users who are already members of the organization cannot be invited to individual groups — they already have cascade access to every group.

### New: Team management endpoints

You can now manage team members and pending invites programmatically. These endpoints unblock automated user provisioning, role changes, and cleanup without touching the AthenaHQ dashboard.

**List members and access**

* **[GET /api/v1/organizations/members](/api-reference/team-management/list-organization-members)** — Lists all members of your organization with their email, name, and role. Requires a global API key.
* **[GET /api/v1/websites/{website_id}/members](/api-reference/team-management/list-website-members)** — Lists all users with access to a website, tagged with `access_type` (`organization` for implicit access via org membership, `website` for explicit website membership). Accepts a global or website-scoped API key.
* **[GET /api/v1/users/{user_id}/websites](/api-reference/team-management/list-user-websites)** — Lists the websites a specific user has access to within your organization. Requires a global API key.

**Update and remove members**

* **[PATCH /api/v1/organizations/members/{user_id}](/api-reference/team-management/update-organization-member-role)** — Update an organization member's role. Requires a global API key.
* **[DELETE /api/v1/organizations/members/{user_id}](/api-reference/team-management/remove-organization-member)** — Remove a user from your organization. Also cleans up any stray website-member rows across all websites in the organization, in a single atomic transaction. Requires a global API key.
* **[PATCH /api/v1/websites/{website_id}/members/{user_id}](/api-reference/team-management/update-website-member-role)** — Update a website member's role. Accepts a global or website-scoped API key.
* **[DELETE /api/v1/websites/{website_id}/members/{user_id}](/api-reference/team-management/remove-website-member)** — Remove a user's explicit access to a website. Accepts a global or website-scoped API key.

**Revoke pending invites**

* **[DELETE /api/v1/invites/org/{invite_id}](/api-reference/team-management/revoke-organization-invite)** — Soft-revoke a pending organization invite. Requires a global API key.
* **[DELETE /api/v1/invites/website/{invite_id}](/api-reference/team-management/revoke-website-invite)** — Soft-revoke a pending website invite. Accepts a global or website-scoped API key.

Mutating endpoints include safety guards: you cannot modify the API key's owning user, the organization owner, or the last remaining admin of an organization.

### Updated: Direct invite responses now include `user_id`

[POST /api/v1/invites](/api-reference/team-management/create-invite) and [POST /api/v1/invites/bulk](/api-reference/team-management/bulk-invite-to-websites-or-groups) now return `user_id` in their success responses **for `invite_method: "direct"` only**. In direct mode, the user is looked up or created immediately, so returning the ID gives you everything you need to call the new member-management endpoints without a discovery round-trip. Email-method invites intentionally do not return `user_id` — doing so would let any API key holder enumerate account existence across tenants by email. This change is backward-compatible; existing integrations continue to work unchanged.

### New: Provision Website endpoint (Beta)

You can now create a fully configured website in a single API call, skipping the onboarding wizard. Optionally set up competitors, prompts, and a processing schedule — all in one request. This endpoint is currently in beta — the request and response schemas may change.

* **[POST /api/v1/websites/provision](/api-reference/basics/provision-website)** — Creates a website with optional competitors, prompts, and schedule. Requires a global API key.

### New: Get group credits

You can now retrieve the current credit balance for a group, including the monthly and one-off breakdown.

* **[GET /api/v1/credits/group](/api-reference/billing/get-group-credits)** — Returns the credit balance for a specific group. Requires a global API key.

## March 2026

### New: Get and update individual groups

You can now fetch a single group by ID and update it — rename the group, add websites, or remove websites.

* **[GET /api/v1/groups/{group_id}](/api-reference/groups/get-group)** — Retrieve a single group with its associated websites.
* **[PATCH /api/v1/groups/{group_id}](/api-reference/groups/update-group)** — Rename a group, add websites, and/or remove websites.

Both endpoints require a global API key.

### New: Bulk invite to websites

You can now invite a user to multiple websites in a single API call. The endpoint processes each website independently and returns per-website results, making it easy to onboard users across your organization.

* **[POST /api/v1/invites/bulk](/api-reference/team-management/bulk-invite-to-websites-or-groups)** — Invite a user to up to 50 websites at once. Supports both direct and email invite methods.

## February 2026

### New: Groups endpoints

You can now create and manage groups of websites via the API. Groups let you organize multiple websites under a single entity within your organization.

* **[GET /api/v1/groups](/api-reference/groups/list-groups)** — List all groups for your organization, including associated websites.
* **[POST /api/v1/groups](/api-reference/groups/create-group)** — Create a new group with one or more websites.

Both endpoints require a global API key.
