# Actions Source: https://docs.topify.ai/api-reference/actions Manage AI-generated recommendations for improving your brand visibility in AI search results. Actions are AI-generated recommendations for improving your brand's visibility in AI search results. There are four action categories: `on_page_seo` (advisory SEO changes), `new_content` (new articles with full write workflow), `content_update` (page edit suggestions), and `forum_engagement` (Reddit reply drafts). ## List actions ``` GET /projects/{project_id}/actions ``` Returns all actions for a project, optionally filtered by status or group. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Query parameters | Param | Type | Default | Description | | -------- | ------ | ------- | --------------------------------------------------------------------------------- | | `status` | string | -- | Filter by status (`suggested`, `accepted`, `in_progress`, `completed`, `ignored`) | | `group` | string | -- | Filter by group name | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions?status=suggested" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/actions", params={"status": "suggested"}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions?status=suggested`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": [ { "id": "a1b2c3d4-...", "priority": "high", "title": "Add structured data to pricing page", "description": "Adding FAQ schema markup to your pricing page will help AI models extract and cite your pricing details.", "category": "on_page_seo", "effort": "quick-win", "expected_impact_min": 5, "expected_impact_max": 15, "status": "suggested", "targets": [ { "type": "url", "value": "https://example.com/pricing" } ], "execution": { "type": "manual", "prompt": null, "skill": null, "params": null }, "group": "Technical SEO", "depends_on": [], "created_at": "2026-04-01T12:00:00Z", "updated_at": "2026-04-01T12:00:00Z" } ] } ``` ### ActionItem fields | Field | Type | Nullable | Description | | --------------------- | ------------- | -------- | --------------------------------------------------------------------- | | `id` | string (UUID) | No | Action ID | | `priority` | string | No | `critical`, `high`, `medium`, or `low` | | `title` | string | No | Short action title | | `description` | string | No | Detailed explanation of the recommendation | | `category` | string | No | `on_page_seo`, `new_content`, `content_update`, or `forum_engagement` | | `effort` | string | Yes | `quick-win`, `moderate`, or `major` | | `expected_impact_min` | number | Yes | Minimum estimated visibility impact (percentage points) | | `expected_impact_max` | number | Yes | Maximum estimated visibility impact (percentage points) | | `status` | string | No | `suggested`, `accepted`, `in_progress`, `completed`, or `ignored` | | `targets` | object\[] | No | Array of targets (see below) | | `execution` | object | No | Execution configuration (see below) | | `group` | string | Yes | Logical grouping name | | `depends_on` | string\[] | No | IDs of actions this action depends on | | `created_at` | string | No | ISO 8601 timestamp | | `updated_at` | string | No | ISO 8601 timestamp | ### Target object | Field | Type | Description | | ------- | ------ | ------------------------------------------ | | `type` | string | `url`, `keyword`, `page_type`, or `domain` | | `value` | string | The target value | ### Execution object | Field | Type | Nullable | Description | | -------- | ------ | -------- | ----------------------------------------------------------- | | `type` | string | No | `prompt`, `publishable_content`, `forum_reply`, or `manual` | | `prompt` | string | Yes | LLM prompt used for execution | | `skill` | string | Yes | Agent skill identifier | | `params` | object | Yes | Additional execution parameters | *** ## Get single action ``` GET /projects/{project_id}/actions/{action_id} ``` Returns a single action by ID. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | | `action_id` | string (UUID) | The action ID | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/actions/{action_id}") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "id": "a1b2c3d4-...", "priority": "high", "title": "Add structured data to pricing page", "description": "Adding FAQ schema markup to your pricing page will help AI models extract and cite your pricing details.", "category": "on_page_seo", "effort": "quick-win", "expected_impact_min": 5, "expected_impact_max": 15, "status": "suggested", "targets": [ { "type": "url", "value": "https://example.com/pricing" } ], "execution": { "type": "manual", "prompt": null, "skill": null, "params": null }, "group": "Technical SEO", "depends_on": [], "created_at": "2026-04-01T12:00:00Z", "updated_at": "2026-04-01T12:00:00Z" } } ``` ### Error responses | Status | Cause | | ------ | -------------------------------- | | `404` | Action not found in this project | *** ## Create action ``` POST /projects/{project_id}/actions ``` Create a new action manually. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Request body | Field | Type | Required | Description | | --------------------- | --------- | -------- | --------------------------------------------------------------------- | | `title` | string | Yes | Short action title | | `description` | string | Yes | Detailed explanation | | `category` | string | Yes | `on_page_seo`, `new_content`, `content_update`, or `forum_engagement` | | `priority` | string | Yes | `critical`, `high`, `medium`, or `low` | | `execution` | object | Yes | `{type, prompt?, skill?}` | | `targets` | object\[] | No | Array of `{type, value}` targets | | `effort` | string | No | `quick-win`, `moderate`, or `major` | | `expected_impact_min` | number | No | Minimum estimated impact | | `expected_impact_max` | number | No | Maximum estimated impact | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "title": "Write a comparison article", "description": "Create an article comparing your product with top competitors for the keyword \"best project management tools\".", "category": "new_content", "priority": "high", "execution": {"type": "publishable_content", "skill": "write_article"}, "targets": [{"type": "keyword", "value": "best project management tools"}], "effort": "major", "expected_impact_min": 10, "expected_impact_max": 25 }' ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/actions", json={ "title": "Write a comparison article", "description": "Create an article comparing your product with top competitors.", "category": "new_content", "priority": "high", "execution": {"type": "publishable_content", "skill": "write_article"}, "targets": [{"type": "keyword", "value": "best project management tools"}], }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ title: "Write a comparison article", description: "Create an article comparing your product with top competitors.", category: "new_content", priority: "high", execution: { type: "publishable_content", skill: "write_article" }, targets: [{ type: "keyword", value: "best project management tools" }], }), }); ``` ### Response Returns the created action (same shape as [get single action](#get-single-action)) with status `suggested`. ### Error responses | Status | Cause | | ------ | ------------------------------------------------------------------- | | `400` | Missing required fields or invalid category/priority/execution type | *** ## Enrich action ``` POST /projects/{project_id}/actions/enrich ``` Sends partial action data to the LLM and returns enriched fields (improved title, description, targets, execution config). ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Request body | Field | Type | Required | Description | | -------------- | --------- | -------- | --------------------------------------------------------------------- | | `action_type` | string | Yes | `on_page_seo`, `new_content`, `content_update`, or `forum_engagement` | | `title` | string | No | Draft title | | `description` | string | No | Draft description | | `target_url` | string | No | Target URL for the action | | `keywords` | string\[] | No | Related keywords | | `instructions` | string | No | Free-form instructions for the LLM | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/enrich" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "action_type": "new_content", "title": "Comparison article", "keywords": ["best project management tools"] }' ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/actions/enrich", json={ "action_type": "new_content", "title": "Comparison article", "keywords": ["best project management tools"], }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/enrich`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ action_type: "new_content", title: "Comparison article", keywords: ["best project management tools"], }), }); ``` ### Response Returns LLM-enriched action fields (title, description, targets, execution, effort, expected impact). ### Error responses | Status | Cause | | ------ | -------------------------------------- | | `400` | Missing `action_type` or invalid value | *** ## Transition status ``` PATCH /projects/{project_id}/actions/{action_id}/{action} ``` Transition an action to a new status. The `action` path parameter determines the transition. ### Path parameters | Param | Type | Description | | ------------ | ------------- | --------------------------------------------------------- | | `project_id` | string (UUID) | The project ID | | `action_id` | string (UUID) | The action ID | | `action` | string | One of: `accept`, `start`, `complete`, `ignore`, `reopen` | ### Request body (ignore only) | Field | Type | Required | Description | | ---------- | ------ | -------- | ------------------- | | `reason` | string | No | Reason for ignoring | | `feedback` | string | No | Additional feedback | ### State machine ``` suggested --> accepted --> in_progress --> completed | ^ +--> ignored ---(reopen)---> accepted ---+ ``` ```bash theme={null} curl -X PATCH "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}/accept" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.patch(f"/projects/{project_id}/actions/{action_id}/accept") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}/accept`, { method: "PATCH", headers, }); ``` ### Response Returns the updated action with the new status. ### Error responses | Status | Cause | | ------ | ---------------------------------------------------------- | | `400` | Invalid transition (e.g., completing a `suggested` action) | | `404` | Action not found | *** ## Batch transition ``` POST /projects/{project_id}/actions/batch-transition ``` Transition multiple actions at once. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Request body | Field | Type | Required | Description | | -------- | --------- | -------- | ---------------------------------------------------- | | `ids` | string\[] | Yes | Array of action IDs | | `action` | string | Yes | `accept`, `start`, `complete`, `ignore`, or `reopen` | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/batch-transition" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "ids": ["a1b2c3d4-...", "e5f6g7h8-..."], "action": "accept" }' ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/actions/batch-transition", json={ "ids": ["a1b2c3d4-...", "e5f6g7h8-..."], "action": "accept", }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/batch-transition`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ ids: ["a1b2c3d4-...", "e5f6g7h8-..."], action: "accept", }), }); ``` ### Response ```json theme={null} { "success": true, "data": { "succeeded": 2, "failed": 0, "total": 2 } } ``` ### Error responses | Status | Cause | | ------ | ------------------------------------- | | `400` | Empty `ids` array or invalid `action` | *** ## Recommend actions ``` POST /projects/{project_id}/actions/recommend ``` Triggers AI-powered action recommendation generation as a background task. The AI analyzes your project data and generates a set of recommended actions. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/recommend" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/actions/recommend") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/recommend`, { method: "POST", headers, }); ``` ### Response ```json theme={null} { "success": true, "data": { "task_id": "f1e2d3c4-..." } } ``` Use the [check recommendation task status](#check-recommendation-task-status) endpoint to poll for completion. *** ## Check recommendation task status ``` GET /projects/{project_id}/actions/tasks/{task_id} ``` Check the status of a background recommendation task. ### Path parameters | Param | Type | Description | | ------------ | ------------- | ---------------------------------------------- | | `project_id` | string (UUID) | The project ID | | `task_id` | string (UUID) | The task ID returned by the recommend endpoint | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/tasks/{task_id}" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/actions/tasks/{task_id}") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/tasks/${taskId}`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "status": "completed", "item_count": 12, "error": null } } ``` ### Task status values | Status | Description | | ----------- | --------------------------------------------- | | `running` | Task is still in progress | | `completed` | Recommendations generated successfully | | `failed` | Task encountered an error (see `error` field) | *** ## Enrich content ``` POST /projects/{project_id}/actions/{action_id}/enrich-content ``` For `content_update` actions. Scrapes the target page via Firecrawl, then generates SEO edit suggestions via LLM. Returns an array of specific edits to improve AI visibility. This endpoint may take 10--20 seconds to respond as it scrapes and analyzes the target page. ### Path parameters | Param | Type | Description | | ------------ | ------------- | ------------------------------------------------- | | `project_id` | string (UUID) | The project ID | | `action_id` | string (UUID) | The action ID (must be a `content_update` action) | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}/enrich-content" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/actions/{action_id}/enrich-content") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}/enrich-content`, { method: "POST", headers, }); ``` ### Response ```json theme={null} { "success": true, "data": [ { "section": "Introduction paragraph", "type": "replace", "before": "We offer project management software.", "after": "We offer AI-powered project management software trusted by 10,000+ teams worldwide.", "signal": "authority", "reason": "Adding specificity and social proof helps AI models identify your brand as a credible source." }, { "section": "FAQ section", "type": "insert", "before": null, "after": "

Frequently Asked Questions

...", "signal": "structured_data", "reason": "FAQ sections are heavily cited by AI models when answering user questions." } ] } ``` ### Edit object fields | Field | Type | Nullable | Description | | --------- | ------ | -------- | ------------------------------------------------ | | `section` | string | No | The page section being edited | | `type` | string | No | `replace`, `insert`, or `delete` | | `before` | string | Yes | Original text (null for `insert`) | | `after` | string | Yes | Replacement text (null for `delete`) | | `signal` | string | No | The SEO/GEO signal being addressed | | `reason` | string | No | Explanation of why this edit improves visibility | ### Error responses | Status | Cause | | ------ | ------------------------------------------------------------ | | `400` | Action is not a `content_update` action or has no target URL | | `404` | Action not found | *** ## Enrich forum ``` POST /projects/{project_id}/actions/{action_id}/enrich-forum ``` For `forum_engagement` actions. Regenerates a Reddit comment draft tailored to the target thread. ### Path parameters | Param | Type | Description | | ------------ | ------------- | --------------------------------------------------- | | `project_id` | string (UUID) | The project ID | | `action_id` | string (UUID) | The action ID (must be a `forum_engagement` action) | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}/enrich-forum" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/actions/{action_id}/enrich-forum") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}/enrich-forum`, { method: "POST", headers, }); ``` ### Response ```json theme={null} { "success": true, "data": { "comment": "I've been using Acme for about six months now and it's been solid for our team...", "comment_word_count": 87, "thread_title": "What project management tool do you use?", "thread_url": "https://www.reddit.com/r/productivity/comments/..." } } ``` ### Error responses | Status | Cause | | ------ | ----------------------------------------- | | `400` | Action is not a `forum_engagement` action | | `404` | Action not found | *** ## Execute action ``` POST /projects/{project_id}/actions/{action_id}/execute ``` Starts the execution workflow for a `new_content` action with `publishable_content` execution type. This triggers article generation with human-in-the-loop approval checkpoints (outline, draft, final). Progress is delivered via [webhooks](/api-reference/webhooks). Only works for actions with `execution.type: "publishable_content"`. You must have a webhook registered for `action.checkpoint` events to receive approval prompts. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | | `action_id` | string (UUID) | The action ID | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}/execute" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/actions/{action_id}/execute") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}/execute`, { method: "POST", headers, }); ``` ### Response (202 Accepted) ```json theme={null} { "success": true, "data": { "workflow_id": "w1x2y3z4-...", "status": "started" } } ``` ### Error responses | Status | Cause | | ------ | --------------------------------------------------------------------------------------- | | `400` | Action is not a `publishable_content` type or is not in `accepted`/`in_progress` status | | `404` | Action not found | *** ## Respond to checkpoint ``` POST /projects/{project_id}/actions/{action_id}/execute/respond ``` Respond to an approval checkpoint in an active execution workflow. Called after receiving an `action.checkpoint` webhook event. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | | `action_id` | string (UUID) | The action ID | ### Request body | Field | Type | Required | Description | | ---------------- | ------------- | -------- | -------------------------------------------------------------------- | | `workflow_id` | string (UUID) | Yes | The workflow ID from the checkpoint event | | `decision` | string | Yes | `approve`, `edit`, `regenerate`, or `reject` | | `edited_content` | string | No | Modified content (required when `decision` is `edit`) | | `feedback` | string | No | Instructions for regeneration (used when `decision` is `regenerate`) | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}/execute/respond" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "w1x2y3z4-...", "decision": "approve" }' ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/actions/{action_id}/execute/respond", json={ "workflow_id": "w1x2y3z4-...", "decision": "approve", }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}/execute/respond`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ workflow_id: "w1x2y3z4-...", decision: "approve", }), }); ``` ### Response ```json theme={null} { "success": true, "data": { "status": "continuing" } } ``` ### Error responses | Status | Cause | | ------ | -------------------------------------------------------------------------------------- | | `400` | Invalid decision, missing `workflow_id`, or `edited_content` required but not provided | | `404` | Action or workflow not found | | `409` | Workflow is not currently waiting at a checkpoint | *** ## Get action state ```http theme={null} GET /projects/{project_id}/actions/{action_id}/state ``` Returns an agent-facing lifecycle snapshot without changing the action. The `next_actions` array contains ready-to-use HTTP instructions for the next valid transitions or checkpoint decisions. ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}/state" \ -H "X-API-Key: tk_live_..." ``` ### Response ```json theme={null} { "success": true, "data": { "action_id": "a1b2c3d4-...", "status": "in_progress", "lifecycle": "awaiting_decision", "current_checkpoint": "article_review", "workflow_id": "w1x2y3z4-...", "workflow_status": "pending", "ready_artifacts": ["research", "outline", "article"], "next_actions": [ { "name": "approve", "label": "Approve and finalize", "http": { "method": "POST", "path": "/api/public/v1/projects/{project_id}/actions/{action_id}/execute/respond", "body": { "workflow_id": "w1x2y3z4-...", "decision": "approve" } } } ] } } ``` Repeated polling is safe. A service request cannot use an action ID or task ID from a different project. *** ## List action artifacts ```http theme={null} GET /projects/{project_id}/actions/{action_id}/artifacts ``` Lists artifact names that are ready to fetch for an action. | Action type | Possible artifact names | | ----------------------------- | -------------------------------- | | Forum engagement | `thread`, `comment` | | On-page SEO or content update | `edits` | | Article workflow | `research`, `outline`, `article` | ```json theme={null} { "success": true, "data": { "artifacts": ["research", "outline"] } } ``` *** ## Get action artifact ```http theme={null} GET /projects/{project_id}/actions/{action_id}/artifacts/{name} ``` Returns one named artifact. The `article` artifact is a publish kit containing the article body, slug, frontmatter, SEO metadata, JSON-LD, and publishing instructions. ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}/artifacts/article" \ -H "X-API-Key: tk_live_..." ``` ### Error responses | Status | Code | Cause | | ------ | ------------------ | ------------------------------------------------------------------------------ | | `400` | `INVALID_ARTIFACT` | The artifact name does not apply to this action type | | `404` | `NOT_FOUND` | The action does not exist in this project | | `409` | `NOT_READY` | The artifact is valid but the workflow has not reached the required checkpoint | *** ## Store discarded edit ```http theme={null} POST /projects/{project_id}/actions/{action_id}/discard-edit ``` Stores an in-progress draft in recovery history without advancing or cancelling the workflow. This is a recoverable draft operation, not prompt deletion. ### Request body | Field | Type | Required | Description | | ---------------- | ------ | -------- | ------------------------------------ | | `workflow_id` | string | Yes | Active workflow ID | | `edited_content` | string | Yes | Non-empty draft content to retain | | `checkpoint` | string | No | Checkpoint associated with the draft | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}/discard-edit" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "w1x2y3z4-...", "edited_content": "Draft article markdown", "checkpoint": "article_review" }' ``` The response returns `discarded_edits`, most recent last. A workflow retains at most 20 entries. *** ## List discarded edits ```http theme={null} GET /projects/{project_id}/actions/{action_id}/discarded-edits ``` Returns the current workflow's recoverable draft history. This endpoint is read-only. ```json theme={null} { "success": true, "data": { "discarded_edits": [ { "content": "Draft article markdown", "checkpoint": "article_review", "timestamp": "2026-07-13T12:00:00.000Z" } ] } } ``` # Agent settings Source: https://docs.topify.ai/api-reference/agent-settings Validate publishing integration credentials before using them in an agent workflow. ## Test Webflow connection ```http theme={null} POST /projects/{project_id}/agent/settings/test-webflow ``` Tests Webflow credentials and returns the collections available to those credentials. This endpoint performs a connection test only; it does not save the credentials. ### Request body | Field | Type | Required | Description | | ------------ | ------------- | -------- | ------------------------------------ | | `project_id` | string (UUID) | Yes | Same project ID used in the URL path | | `site_id` | string | Yes | Webflow site ID | | `api_token` | string | Yes | Webflow API token | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/agent/settings/test-webflow" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "project_id": "a1b2c3d4-...", "site_id": "65abc123...", "api_token": "webflow-token" }' ``` ### Response ```json theme={null} { "success": true, "data": { "success": true, "error": null, "collections": [ { "id": "collection-id", "title": "Blog posts" } ] } } ``` Treat the Webflow token as a secret. Do not log it or include it in agent conversation history. ### Error responses | Status | Cause | | ------ | -------------------------------------------------------------- | | `400` | Missing project ID, site ID, API token, or invalid JSON | | `403` | The API key's team cannot access the project | | `500` | Webflow rejected the credentials or the connection test failed | # Authorized AI agent setup Source: https://docs.topify.ai/api-reference/agentic-setup Set up Topify with a tool-using AI agent after the user authorizes the account, team, and project details. Use tool\_use to call the endpoints below directly from Claude Code. Cursor's agent mode can call these endpoints via HTTP requests. Any tool-use-capable LLM agent can complete this flow. These endpoints let AI agents set up Topify.ai from scratch and generate a full GEO visibility report using account details explicitly supplied or authorized by the user. The full flow is three API calls: 1. **Create account** -- register with an email, receive credentials instantly 2. **Get API key** -- exchange credentials for an API key 3. **Create project** -- start brand tracking and receive results via webhook Use an email address supplied or explicitly authorized by the user. Agents must not invent a temporary email or admin identity. API keys can only be created by admins and currently grant access to the full team; project-scoped API keys are not available. Run this flow only in a trusted server-side environment. Never expose the generated password or API key in browser code, client-side applications, logs, prompts, chat history, or tool output shown to other users. These endpoints are designed for **tool-use AI agents** such as Claude Code, Cursor, and OpenClaw that can make HTTP requests programmatically. If you are using a web-based AI chatbot (ChatGPT, Perplexity, etc.) that cannot call APIs directly, create your account and project manually at [app.topify.ai](https://app.topify.ai), then use the **Get API key** endpoint with your email and password to obtain a key for the read-only data endpoints. *** ## Credits and rate limits Every API key has a **rate limit tier** and a **monthly credit allowance**. Credits are consumed when projects fetch AI responses during bootstrap and daily refresh cycles. ### Rate limit tiers Rate limits use a sliding 60-second window. The burst allowance lets you briefly exceed the base rate. | Tier | Requests / minute | Burst | Effective limit / window | | -------- | ----------------- | ----- | ------------------------ | | Standard | 60 | +10 | 70 | | Premium | 300 | +50 | 350 | New API keys are created on the **standard** tier. Contact us to upgrade to premium. When the limit is exceeded, the API returns `429 Too Many Requests` with a `Retry-After` header indicating how many seconds to wait. ### Research credits by plan Each plan includes a monthly credit budget that determines how many AI research queries your projects can run. | Plan | Monthly credits | | --------------------------------------------- | --------------- | | Skip Trial (default for API-created accounts) | 10 | | Basic | 200 | | Pro | 500 | | Enterprise | 2,000 | Accounts created via the API start on the **skip\_trial** plan with 10 credits. Each project bootstrap consumes credits proportional to the number of prompts and AI providers queried (typically 5 prompts across 3 providers = 15 credits per full bootstrap cycle). The skip\_trial plan runs a reduced bootstrap that fits within the 10-credit budget. For a full bootstrap with all prompts and providers, upgrade to the Basic plan or higher from the [dashboard](https://app.topify.ai) or contact us. ### Response headers Every response includes rate limit and credit information: | Header | Description | | ----------------------- | ----------------------------------------------- | | `X-RateLimit-Limit` | Maximum requests allowed in the current window | | `X-RateLimit-Remaining` | Requests remaining before throttling | | `X-Credit-Available` | Research credits remaining on your key | | `Retry-After` | Seconds until the window resets (only on `429`) | *** ## Create account ``` POST /account/create ``` Creates a new Topify.ai account with a team on the `skip_trial` plan. The account is created without email verification, so it can be used immediately. No authentication is required for this endpoint. The generated password is returned once and cannot be retrieved later -- store it securely. ### Request body | Field | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------------------------------------------------------------------- | | `email` | string | Yes | Email address supplied or explicitly authorized by the user. Do not generate a temporary identity | | `password` | string | No | Ignored. A secure password is generated server-side | ```json theme={null} { "email": "user@example.com" } ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "team_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "email": "user@example.com", "password": "" } } ``` ### Response fields | Field | Type | Description | | ---------- | ------------- | -------------------------------------------- | | `user_id` | string (UUID) | The new user's ID | | `team_id` | string (UUID) | The team created for this account | | `email` | string | The normalized email address | | `password` | string | Auto-generated password. Store this securely | ### Errors | Status | Detail | Cause | | ------ | ---------------------------------------------------------------- | ------------------------------------- | | `400` | `A valid email is required` | Email is empty or missing `@` | | `409` | `Failed to create account. The email may already be registered.` | A user with this email already exists | **If you receive a 409:** The account already exists. Skip to the **Get API key** step using the same email and the user's existing password. If the password is unknown, ask the user or direct them to reset it at [app.topify.ai](https://app.topify.ai). *** ## Get API key ``` POST /account/api-key ``` Authenticates a team administrator with email and password, then creates and returns a new API key on the **standard** rate limit tier. The key can access every project owned by the selected team; project-scoped keys are not currently issued. ### Request body | Field | Type | Required | Description | | ---------- | ------------- | -------- | -------------------------------------------------------------------------------------- | | `email` | string | Yes | Account email address | | `password` | string | Yes | Account password (from the create endpoint) | | `team_id` | string (UUID) | No | Team to create the key for. Required when the administrator manages more than one team | ```json theme={null} { "email": "user@example.com", "password": "" } ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "api_key": "tk_live_", "team_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "key_prefix": "tk_live_214d", "rate_limit_tier": "standard" } } ``` ### Response fields | Field | Type | Description | | ----------------- | ------------- | --------------------------------------------------------------------------------------------- | | `api_key` | string | Full API key. Store it only in a server-side secret manager or protected environment variable | | `team_id` | string (UUID) | The team this key belongs to | | `key_prefix` | string | First 12 characters of the key, for identification | | `rate_limit_tier` | string | Rate limit tier assigned to this key (`standard` or `premium`) | ### Errors | Status | Detail | Cause | | ------ | ------------------------------------ | ------------------------------------------------------------------- | | `401` | `Invalid email or password` | Authentication failed | | `403` | `No admin team found for this user` | Only team administrators can create or retrieve public API keys | | `400` | `team_id is required` | The administrator manages multiple teams; specify the intended team | | `500` | `Authentication service unavailable` | The authentication service is temporarily unavailable | *** ## Create project ``` POST /account/projects ``` Creates a new brand tracking project and starts the bootstrap pipeline in the background. The endpoint returns immediately with a `202 Accepted` status while the pipeline runs asynchronously. The bootstrap pipeline generates tracking prompts, fetches initial AI responses from all providers, calculates brand metrics, and detects competitors. When complete, a webhook callback is sent to the URL you provide. Requires API key authentication via the `X-API-Key` header. ### Request body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------ | | `brand_name` | string | Yes | The brand name to track | | `brand_url` | string | Yes | The brand's website URL | | `webhook_url` | string | Yes | URL to receive the completion callback | | `language` | string | No | Language for generated prompts (e.g. `en`, `ja`) | | `location` | string | No | Target market country code (e.g. `US`, `JP`) | ```json theme={null} { "brand_name": "Acme Corp", "brand_url": "https://acme.com", "webhook_url": "https://your-service.com/webhooks/topify", "language": "en", "location": "US" } ``` ### Response ```json theme={null} { "code": 202, "message": "202 Accepted", "data": { "project_id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "status": "initializing" } } ``` ### Response fields | Field | Type | Description | | ------------ | ------------- | ------------------------------------- | | `project_id` | string (UUID) | The new project's ID | | `status` | string | Initial status. Always `initializing` | ### Webhook callback When the bootstrap pipeline completes (or fails), a POST request is sent to your `webhook_url` with the following payload: **Success:** ```json theme={null} { "event": "project.bootstrap", "project_id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "status": "completed", "error": null, "timestamp": "2026-03-10T03:27:35Z" } ``` **Failure:** ```json theme={null} { "event": "project.bootstrap", "project_id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "status": "error", "error": "Failed to generate prompts", "timestamp": "2026-03-10T03:27:35Z" } ``` Treat the bootstrap callback only as a completion signal. Match its `project_id` to the ID returned by your create request, then confirm the current project state through an authenticated API request before performing any follow-up mutation. ### Errors | Status | Detail | Cause | | ------ | -------------------------------------------------- | ----------------------------- | | `400` | `brand_name is required` | Missing or empty brand name | | `400` | `brand_url must be a valid URL` | URL failed validation | | `400` | `webhook_url must be a valid URL` | Webhook URL failed validation | | `401` | `Missing X-API-Key header` | No API key provided | | `403` | `You've reached the maximum number of projects...` | Plan project limit reached | *** ## After bootstrap completes After the webhook reports `"status": "completed"` and you confirm the project through the authenticated API, use these read-only endpoints (documented in [API reference](/api-reference/introduction)) to retrieve results: | Endpoint | What it returns | | ----------------------------------------------- | ------------------------------------------------------- | | `GET /projects` | List of projects with current brand metrics | | `GET /projects/{id}/overview` | Per-prompt aggregated analysis with competitor mentions | | `GET /projects/{id}/visibility?duration_days=7` | Daily visibility, sentiment, and position trends | | `GET /projects/{id}/prompts` | All tracked prompts with per-provider metrics | | `GET /projects/{id}/competitors` | Detected competitor brands and their metrics | | `GET /projects/{id}/sources` | Domains cited by AI providers, sorted by citation count | All data endpoints require the `X-API-Key` header and return responses in the format `{"success": true, "data": {...}}`. For a quick summary to present to the user, start with `GET /projects` to get the brand's overall visibility score and sentiment, then `GET /projects/{id}/overview` for a prompt-level breakdown. *** ## Full workflow example The workflow can run end-to-end after the user supplies or explicitly authorizes the account email. Do not generate a temporary identity. The API key created in step 2 is an admin-created, full-team credential. ```bash theme={null} # 1. Create an account curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/account/create" \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com"}' # 2. Get an API key (use the password from step 1) curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/account/api-key" \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "password": "Tp..."}' # 3. Create a project (use the API key from step 2) curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/account/projects" \ -H "Content-Type: application/json" \ -H "X-API-Key: tk_live_..." \ -d '{ "brand_name": "Acme Corp", "brand_url": "https://acme.com", "webhook_url": "https://your-service.com/webhooks/topify" }' # 4. Query project data after webhook confirms completion curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} import httpx BASE = "https://topify-customer-api-production.up.railway.app/api/public/v1" # 1. Create account resp = httpx.post(f"{BASE}/account/create", json={ "email": "user@example.com", }) account = resp.json()["data"] password = account["password"] # 2. Get API key resp = httpx.post(f"{BASE}/account/api-key", json={ "email": "user@example.com", "password": password, }) api_key = resp.json()["data"]["api_key"] # 3. Create project headers = {"X-API-Key": api_key} resp = httpx.post(f"{BASE}/account/projects", json={ "brand_name": "Acme Corp", "brand_url": "https://acme.com", "webhook_url": "https://your-service.com/webhooks/topify", }, headers=headers) project_id = resp.json()["data"]["project_id"] print(f"Project {project_id} is bootstrapping...") # 4. After webhook confirms completion, query data resp = httpx.get(f"{BASE}/projects", headers=headers) print(resp.json()) ``` ```javascript theme={null} const BASE = "https://topify-customer-api-production.up.railway.app/api/public/v1"; // 1. Create account let resp = await fetch(`${BASE}/account/create`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: "user@example.com" }), }); const { data: account } = await resp.json(); // 2. Get API key resp = await fetch(`${BASE}/account/api-key`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: "user@example.com", password: account.password, }), }); const { data: keyData } = await resp.json(); // 3. Create project const headers = { "Content-Type": "application/json", "X-API-Key": keyData.api_key, }; resp = await fetch(`${BASE}/account/projects`, { method: "POST", headers, body: JSON.stringify({ brand_name: "Acme Corp", brand_url: "https://acme.com", webhook_url: "https://your-service.com/webhooks/topify", }), }); const { data: project } = await resp.json(); console.log(`Project ${project.project_id} is bootstrapping...`); // 4. After webhook confirms completion, query data resp = await fetch(`${BASE}/projects`, { headers }); console.log(await resp.json()); ``` # Brand aliases Source: https://docs.topify.ai/api-reference/aliases List and manage alternative brand names used when analyzing AI responses. Brand aliases let Topify recognize abbreviations, spacing variations, and product names as references to your brand. A project can have up to 20 aliases. Each alias uses either `fuzzy` matching, which tolerates capitalization and spacing differences, or `exact` matching, which requires whole-word matches. ## List aliases ```http theme={null} GET /projects/{project_id}/aliases ``` ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/aliases" \ -H "X-API-Key: tk_live_..." ``` ### Response ```json theme={null} { "success": true, "data": { "project_id": "a1b2c3d4-...", "brand_name": "Acme", "aliases": [ { "name": "ACME Corp", "match_type": "fuzzy" } ], "limit": 20, "remaining": 19 } } ``` *** ## Add alias ```http theme={null} POST /projects/{project_id}/aliases ``` ### Request body | Field | Type | Required | Description | | ------------ | ------ | -------- | ---------------------------- | | `alias` | string | Yes | Alias text, 1-100 characters | | `match_type` | string | No | `fuzzy` (default) or `exact` | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/aliases" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"alias":"ACME Corp","match_type":"fuzzy"}' ``` The response returns the complete updated alias list, remaining capacity, and a `next_actions` entry for listing aliases. *** ## Update alias ```http theme={null} PATCH /projects/{project_id}/aliases ``` Rename an alias, change its match type, or do both. ### Request body | Field | Type | Required | Description | | ---------------- | ------ | -------- | ------------------------------------------------------------------ | | `original_alias` | string | Yes | Existing alias; matching is case-insensitive | | `new_alias` | string | Yes | Replacement alias text | | `match_type` | string | No | New `fuzzy` or `exact` setting; omit to retain the current setting | ```bash theme={null} curl -X PATCH "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/aliases" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"original_alias":"ACME Corp","new_alias":"Acme Corporation","match_type":"exact"}' ``` *** ## Delete alias ```http theme={null} DELETE /projects/{project_id}/aliases?alias={alias} ``` Deletes one alias using a case-insensitive match. This operation does not delete prompts or historical AI responses. Alias deletion is immediate. Confirm the alias value before sending the request. ```bash theme={null} curl -X DELETE "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/aliases?alias=Acme%20Corporation" \ -H "X-API-Key: tk_live_..." ``` ### Error responses | Status | Code | Cause | | ------ | --------------------------------- | --------------------------------------------------- | | `400` | `ALIAS_EMPTY` or `ALIAS_TOO_LONG` | Alias text is invalid | | `404` | `ALIAS_NOT_FOUND` | The requested alias does not exist | | `409` | `ALIAS_DUPLICATE` | Another case-insensitive alias match already exists | | `429` | `ALIAS_LIMIT_REACHED` | The project already has 20 aliases | # Authentication Source: https://docs.topify.ai/api-reference/authentication Authenticate with the Topify.ai public API using API keys. The public API uses API key authentication. Every request must include your key in the `X-API-Key` header. ## Request header ``` X-API-Key: tk_live_ ``` ## Getting an API key Generate API keys from your Topify.ai dashboard under **Management > Members**, or programmatically via the [agentic setup](/api-reference/agentic-setup) endpoints. Each key is scoped to your team and gives access to all projects owned by that team. Only team admins can create API keys. Project-scoped API keys are not currently issued. Treat each API key as an administrator credential for the full team and store it accordingly. API keys are for trusted server-side use only. Never embed one in browser JavaScript, mobile or desktop client code, public repositories, logs, AI prompts, or chat history. Load it from a secret manager or protected environment variable and rotate it immediately if exposed. The full API key is shown only once when it is created. Store it immediately in a secret manager. The Members page displays only the key prefix; if the full value is lost, create a replacement key and disable the old one. ## Rate limiting Requests are rate-limited per API key. Limits depend on your plan tier: | Tier | Requests per minute | Burst allowance | Total per window | | -------- | ------------------- | --------------- | ---------------- | | Standard | 60 | 10 | 70 | | Premium | 300 | 50 | 350 | ### Response headers Every response includes rate limit information: | Header | Type | Description | | ----------------------- | ------- | ------------------------------------------------------- | | `X-RateLimit-Limit` | integer | Maximum requests allowed in the current window | | `X-RateLimit-Remaining` | integer | Requests remaining before throttling | | `Retry-After` | integer | Seconds until the window resets (only present on `429`) | ### Rate limit exceeded When you exceed the limit, the API returns HTTP `429`: ```json theme={null} { "detail": "Rate limit exceeded. Retry after 42 seconds." } ``` ## Key lifecycle | State | Behavior | | ------------ | ------------------------------------------------- | | **Active** | Key is valid and accepts requests | | **Inactive** | Key has been disabled by an admin. Returns `401` | | **Expired** | Key has passed its expiration date. Returns `401` | You can deactivate or delete keys at any time from the dashboard. ## Example requests ```bash theme={null} curl -X GET "https://topify-customer-api-production.up.railway.app/api/public/v1/projects" \ -H "X-API-Key: ${TOPIFY_API_KEY}" ``` ```python theme={null} import os import httpx client = httpx.Client( base_url="https://topify-customer-api-production.up.railway.app/api/public/v1", headers={"X-API-Key": os.environ["TOPIFY_API_KEY"]}, ) resp = client.get("/projects") print(resp.json()) ``` ```javascript theme={null} const BASE_URL = "https://topify-customer-api-production.up.railway.app/api/public/v1"; const API_KEY = process.env.TOPIFY_API_KEY; if (!API_KEY) throw new Error("TOPIFY_API_KEY is not set"); const resp = await fetch(`${BASE_URL}/projects`, { headers: { "X-API-Key": API_KEY }, }); const data = await resp.json(); console.log(data); ``` ## Error responses | Status | Detail | Cause | | ------ | --------------------------------------------- | ----------------------------------------- | | `401` | `Missing X-API-Key header` | No `X-API-Key` header provided | | `401` | `Invalid API key` | Key is not recognized | | `401` | `API key is inactive` | Key was disabled by an admin | | `401` | `API key has expired` | Key passed its configured expiration date | | `429` | `Rate limit exceeded. Retry after N seconds.` | Too many requests in the current window | # Competitors Source: https://docs.topify.ai/api-reference/competitors Manage competitor brands and retrieve their aggregated metrics. Competitors are the brands Topify.ai tracks alongside yours across AI responses. This endpoint returns active competitors with aggregated visibility, sentiment, and position metrics. ## List competitors ``` GET /projects/{project_id}/competitors ``` Returns all active competitors for a project with aggregated metrics for the given date range. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | ------- | ------------------------------- | | `duration_days` | integer | `7` | Days to look back | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `providers` | string | -- | Comma-separated provider filter | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors?duration_days=7" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/competitors", params={"duration_days": 7}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/competitors?duration_days=7`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "active_competitors": [ { "competitor_id": "00000000-0000-0000-0000-000000000000", "project_id": "a1b2c3d4-...", "name": "Acme Corp", "website": "https://acme.com", "icon_url": "https://logo.clearbit.com/acme.com", "icon_urls": [ "https://logo.clearbit.com/acme.com", "https://www.google.com/s2/favicons?domain=acme.com&sz=64" ], "state": "active", "is_own_brand": true, "metrics": { "visibility": 85.0, "sentiment": 72.0, "position": 1.8, "mention_count": null, "days_with_data": 7, "period": "2026-02-27 to 2026-03-06" } }, { "competitor_id": "c1d2e3f4-...", "project_id": "a1b2c3d4-...", "name": "Competitor A", "website": "https://competitor-a.com", "icon_url": "https://logo.clearbit.com/competitor-a.com", "icon_urls": [ "https://logo.clearbit.com/competitor-a.com", "https://www.google.com/s2/favicons?domain=competitor-a.com&sz=64" ], "state": "active", "is_own_brand": false, "metrics": { "visibility": 72.0, "sentiment": 65.0, "position": 3.1, "mention_count": null, "days_with_data": 7, "period": "2026-02-27 to 2026-03-06" } } ], "total_active": 8 } } ``` ### CompetitorResponse fields | Field | Type | Nullable | Description | | --------------- | ----------------- | -------- | ----------------------------------------------------- | | `competitor_id` | string (UUID) | No | Competitor ID. `00000000-...` for injected own brand | | `project_id` | string (UUID) | No | Parent project ID | | `name` | string | No | Competitor brand name | | `website` | string | No | Competitor website URL | | `icon_url` | string | Yes | Primary logo URL | | `icon_urls` | string\[] | No | Multiple logo URL sources (Clearbit + Google favicon) | | `state` | string | Yes | Always `active` in this endpoint | | `is_own_brand` | boolean | No | `true` if this is your own brand | | `metrics` | CompetitorMetrics | Yes | Aggregated metrics for the date range | ### CompetitorMetrics fields | Field | Type | Nullable | Description | | ---------------- | ------- | -------- | ------------------------------------------------------------ | | `visibility` | float | Yes | Average visibility score across the period | | `sentiment` | float | Yes | Average sentiment score across the period | | `position` | float | Yes | Average position in AI responses | | `mention_count` | integer | Yes | Total mention count (may be `null`) | | `days_with_data` | integer | Yes | Number of days with collected data | | `period` | string | No | Human-readable date range (e.g., `2026-02-27 to 2026-03-06`) | Your own brand is always included in the response with `is_own_brand: true`. If your brand doesn't already exist as a competitor, it is injected automatically with zeroed metrics so you can compare directly. *** ## List pending competitors ``` GET /projects/{project_id}/competitors/pending ``` Returns AI-discovered competitors that are waiting for a track or reject decision. Pending competitors do not have analytics metrics yet. ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors/pending" \ -H "X-API-Key: tk_live_..." ``` ```json theme={null} { "success": true, "data": { "pending_competitors": [ { "competitor_id": "e5f6a7b8-...", "project_id": "a1b2c3d4-...", "name": "Acme", "website": "https://acme.com", "state": "pending", "is_own_brand": false, "metrics": null } ], "total_pending": 1 } } ``` ## Review a pending competitor ``` PATCH /projects/{project_id}/competitors/{competitor_id}/state ``` Track or reject a pending competitor. Send `active` to begin tracking it, or `inactive` to reject it. Only competitors currently in `pending` state can be reviewed through this endpoint. ```bash theme={null} curl -X PATCH "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors/{competitor_id}/state" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"state":"active"}' ``` | Field | Type | Required | Description | | ------- | ------ | -------- | ----------------------------------------- | | `state` | string | Yes | `active` to track or `inactive` to reject | | Status | Cause | | ------ | ------------------------------------ | | `400` | Invalid target state | | `404` | Competitor not found in this project | | `409` | Competitor is not currently pending | *** ## Create competitors ``` POST /projects/{project_id}/competitors ``` Add one or more competitor brands to a project. Each competitor begins tracking immediately. API keys are admin-created and currently have full-team access. Confirm the project and competitor list before creating competitors. ### Path parameters | Param | Type | Description | | ------------ | ------------- | --------------------------------- | | `project_id` | string (UUID) | The project to add competitors to | ### Request body | Field | Type | Required | Description | | ----------------------- | --------- | -------- | ------------------------------------------------------------- | | `competitors` | object\[] | Yes | Array of competitors to add (1--20 per request) | | `competitors[].name` | string | Yes | Brand name | | `competitors[].website` | string | Yes | Website domain or URL (e.g. `acme.com` or `https://acme.com`) | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "competitors": [ {"name": "Acme Corp", "website": "acme.com"}, {"name": "Globex Inc", "website": "globex.net"} ] }' ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/competitors", json={ "competitors": [ {"name": "Acme Corp", "website": "acme.com"}, {"name": "Globex Inc", "website": "globex.net"}, ], }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/competitors`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ competitors: [ { name: "Acme Corp", website: "acme.com" }, { name: "Globex Inc", website: "globex.net" }, ], }), }); ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "projectId": "a1b2c3d4-...", "created": 2, "competitors": [ { "competitorId": "c1d2e3f4-...", "projectId": "a1b2c3d4-...", "name": "Acme Corp", "website": "https://acme.com", "iconUrl": "https://img.logo.dev/acme.com?token=...", "iconUrls": ["https://img.logo.dev/acme.com?token=..."], "state": "active", "isOwnBrand": false, "metrics": null } ] } } ``` ### Error responses | Status | Cause | | ------ | ---------------------------------------------------------------------------------------- | | `400` | Empty competitors array, empty name, invalid website URL, or duplicate domain in request | | `409` | A competitor with the same domain already exists in this project | | `429` | Active competitor limit reached (max 10) | *** ## Update competitor ``` PATCH /projects/{project_id}/competitors/{competitor_id} ``` Update the name or website of an existing competitor. API keys are admin-created and currently have full-team access. Confirm the project and fields before updating a competitor. ### Path parameters | Param | Type | Description | | --------------- | ------------- | ------------------------ | | `project_id` | string (UUID) | The project ID | | `competitor_id` | string (UUID) | The competitor to update | ### Request body All fields are optional. Include only the fields you want to change. | Field | Type | Description | | --------- | ------ | -------------------------------- | | `name` | string | New brand name (cannot be empty) | | `website` | string | New website domain or URL | ```bash theme={null} curl -X PATCH "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors/{competitor_id}" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"name": "Acme Inc", "website": "acmeinc.com"}' ``` ```python theme={null} resp = client.patch(f"/projects/{project_id}/competitors/{competitor_id}", json={ "name": "Acme Inc", }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/competitors/${competitorId}`, { method: "PATCH", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ name: "Acme Inc" }), }); ``` ### Response Returns the updated competitor with all fields (same shape as items in [list competitors](#list-competitors)). ### Error responses | Status | Cause | | ------ | ----------------------------------------------------------------------------------- | | `400` | Empty request body, whitespace-only name, invalid website URL, or no fields changed | | `404` | Competitor not found in this project | *** ## Delete competitor ``` DELETE /projects/{project_id}/competitors/{competitor_id} ``` Permanently remove a competitor from your project and stop tracking it. This action cannot be undone. API keys are admin-created and currently have full-team access. ### Path parameters | Param | Type | Description | | --------------- | ------------- | ------------------------ | | `project_id` | string (UUID) | The project ID | | `competitor_id` | string (UUID) | The competitor to delete | ```bash theme={null} curl -X DELETE "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors/{competitor_id}" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.delete(f"/projects/{project_id}/competitors/{competitor_id}") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/competitors/${competitorId}`, { method: "DELETE", headers, }); ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "competitorId": "c1d2e3f4-...", "message": "Competitor deleted successfully" } } ``` ### Error responses | Status | Cause | | ------ | ------------------------------------ | | `404` | Competitor not found in this project | # Public API Source: https://docs.topify.ai/api-reference/introduction Reference documentation for the Topify.ai public API. The public API lets you read GEO analytics and run approved project workflows, including prompt creation, competitor review, action execution, recorded URL management, reports, and integration checks. **AI agents (Claude Code, Cursor, OpenClaw):** Use account information explicitly supplied or authorized by the user. API keys can only be created by team admins and currently grant access to the full team. See [Agentic setup](/api-reference/agentic-setup). ## Base URL ``` https://topify-customer-api-production.up.railway.app/api/public/v1 ``` All endpoint paths in this reference are relative to this base URL. ## Authentication Every request requires an API key in the `X-API-Key` header. See [authentication](/api-reference/authentication) for details. Use API keys only from trusted server-side code. Current keys grant access to every project owned by the team, so never expose them in a browser, client application, log, prompt, or chat history. ``` X-API-Key: tk_live_ ``` ## Response envelope Most customer API endpoints use this envelope: **Success:** ```json theme={null} { "success": true, "data": { ... } } ``` **Error:** ```json theme={null} { "detail": "Description of what went wrong" } ``` Some setup and write operations, including `/account/*`, prompt writes and generation, and recorded URLs, use `{"code": 200, "message": "200 OK", "data": {...}}`. The report endpoint returns HTML. Check each endpoint's response example before parsing it. ## HTTP status codes | Code | Meaning | | ----- | -------------------------------------------------------------- | | `200` | Success | | `401` | Unauthorized -- missing, invalid, inactive, or expired API key | | `403` | Forbidden -- API key does not have access to this project | | `404` | Not found -- project, prompt, or resource does not exist | | `429` | Rate limit exceeded -- see `Retry-After` header | ## Date range filtering Most analytics endpoints accept these query parameters: | Param | Type | Default | Description | | --------------- | ------- | ------- | -------------------------------------- | | `duration_days` | integer | `7` | Number of days to look back from today | | `date_from` | string | -- | Start date in `YYYY-MM-DD` format | | `date_to` | string | -- | End date in `YYYY-MM-DD` format | If both `duration_days` and `date_from`/`date_to` are provided, the explicit date range takes precedence. Maximum range is 90 days. ## Provider filtering Analytics endpoints accept a `providers` query parameter: ``` ?providers=chatgpt,perplexity,gemini ``` Supported values: `chatgpt`, `perplexity`, `google_ai_overview`, `gemini`, `claude`. Omitting returns data from all providers. ## Pagination Endpoints with large result sets support page-based pagination: | Param | Type | Default | Max | Description | | ----------- | ------- | ------- | ----- | ------------------------- | | `page` | integer | `1` | -- | Page number (starts at 1) | | `page_size` | integer | `50` | `100` | Results per page | ## Rate limit headers Every response includes these headers: | Header | Description | | ----------------------- | ----------------------------------------------- | | `X-RateLimit-Limit` | Total requests allowed per window | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `X-Credit-Available` | API credits remaining on your key | | `Retry-After` | Seconds to wait before retrying (only on `429`) | ## Agentic setup Set up an account and project after the user explicitly authorizes the identity, team, and project details. ## Endpoints List and retrieve tracked projects. Manage alternative names used for brand recognition. Manage URL inputs used for prompt discovery. Prompts, analytics, AI responses, domains, and URLs. Competitor brands with aggregated metrics. Source domains cited in AI responses. Topic groups that organize prompts. Per-prompt aggregated analysis. Time-series competitor metrics over time. Category breakdown, performance trends, and opportunities. Generate self-contained HTML visibility reports. Review recommendations, execute workflows, and fetch artifacts. Test publishing integration credentials. Receive action and project lifecycle events. # Overview Source: https://docs.topify.ai/api-reference/overview Get aggregated prompt-level analysis with competitor mentions. The overview endpoint returns a per-prompt summary of your project's GEO performance -- visibility, sentiment, position, and which competitors appear alongside your brand. Use this to build dashboards or leaderboard views. ## Get overview ``` GET /projects/{project_id}/overview ``` Returns aggregated metrics for all prompts in a project, with competitor mention counts per prompt. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | -------- | --------------------------------- | | `duration_days` | integer | `7` | Days to look back | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `prompt_type` | string | `active` | Filter by `active` or `suggested` | | `providers` | string | -- | Comma-separated provider filter | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/overview?duration_days=7&prompt_type=active" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/overview", params={"duration_days": 7, "prompt_type": "active"}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/overview?duration_days=7&prompt_type=active`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "items": [ { "prompt_id": "e5f6a7b8-...", "content": "What is the best project management tool?", "prompt_type": "active", "created_at": "2026-01-15T10:30:00+00:00", "topic_id": "t1u2v3w4-...", "topic_name": "Product comparisons", "visibility": 85.0, "sentiment": 72.0, "position": 2.3, "volume": 2400, "intent": "commercial", "cvr": 45.0, "competitors_mentioned": [ { "competitor_id": "c1d2e3f4-...", "name": "Competitor A", "mention_count": 15, "icon_url": "https://logo.clearbit.com/competitor-a.com", "icon_urls": [ "https://logo.clearbit.com/competitor-a.com", "https://www.google.com/s2/favicons?domain=competitor-a.com&sz=64" ] } ] } ] } } ``` ### OverviewPromptItem fields | Field | Type | Nullable | Description | | ----------------------- | ---------------------------- | -------- | ------------------------------------------------------------------ | | `prompt_id` | string (UUID) | No | Prompt ID | | `content` | string | No | The AI search query text | | `prompt_type` | string | No | `active`, `pending`, `suggested`, `inactive`, or `url_recommended` | | `created_at` | string (ISO 8601) | No | Prompt creation timestamp | | `topic_id` | string (UUID) | Yes | Topic this prompt belongs to | | `topic_name` | string | Yes | Topic display name | | `visibility` | float | No | Brand visibility percentage (0--100) for the date range | | `sentiment` | float | No | Average brand sentiment score for the date range | | `position` | float | No | Average brand position in responses | | `volume` | integer | Yes | Estimated AI search demand for this prompt | | `intent` | string | Yes | `informational`, `commercial`, `navigational`, or `transactional` | | `cvr` | float | Yes | Content visibility rate (percentage) | | `competitors_mentioned` | OverviewCompetitorMention\[] | No | Competitors that appeared in responses to this prompt | ### OverviewCompetitorMention fields | Field | Type | Nullable | Description | | --------------- | ------------- | -------- | ----------------------------------------------- | | `competitor_id` | string (UUID) | No | Competitor ID | | `name` | string | No | Competitor brand name as it appeared | | `mention_count` | integer | No | Total mentions across responses for this prompt | | `icon_url` | string | Yes | Primary logo URL | | `icon_urls` | string\[] | No | Multiple logo URL sources | ### Errors | Status | Detail | Cause | | ------ | --------------------------------------------- | ------------------------------------- | | `400` | `prompt_type must be 'active' or 'suggested'` | Invalid `prompt_type` query parameter | # Projects Source: https://docs.topify.ai/api-reference/projects List and retrieve your brand tracking projects. Projects represent the brands you track in Topify.ai. Each project has prompts, competitors, sources, and analytics data. ## List projects ``` GET /projects ``` Returns all projects your API key has access to, ordered by creation date (oldest first). ### Request No query parameters. ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get("/projects") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "My Brand Project", "brand_name": "Acme Corp", "website_url": "https://acme.com", "location": "US", "language": "en", "brand_aliases": [], "created_at": "2026-01-15T10:30:00+00:00" } ] } ``` ### Response fields | Field | Type | Nullable | Description | | --------------- | ----------------- | -------- | ---------------------------------------------------------- | | `id` | string (UUID) | No | Project ID | | `name` | string | No | Project display name | | `brand_name` | string | Yes | Primary brand name being tracked | | `website_url` | string | Yes | Brand website URL | | `location` | string | Yes | Target market country code | | `language` | string | Yes | Target language code | | `brand_aliases` | string\[] | No | Always `[]` in list view. Use the get endpoint for aliases | | `created_at` | string (ISO 8601) | No | Creation timestamp | *** ## Get project ``` GET /projects/{project_id} ``` Returns details for a single project, including brand aliases. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "My Brand Project", "brand_name": "Acme Corp", "website_url": "https://acme.com", "location": "US", "language": "en", "brand_aliases": ["ACME", "Acme Corporation", "acme.com"], "created_at": "2026-01-15T10:30:00+00:00" } } ``` ### Response fields Same as list projects, except `brand_aliases` contains the actual aliases: | Field | Type | Nullable | Description | | --------------- | --------- | -------- | --------------------------------------------------------- | | `brand_aliases` | string\[] | No | Alternative brand names used for matching in AI responses | ### Errors | Status | Detail | Cause | | ------ | -------------------------------------- | --------------------------------------- | | `403` | `Project does not belong to your team` | API key's team doesn't own this project | | `404` | `Project not found` | No project with this ID exists | # Prompts Source: https://docs.topify.ai/api-reference/prompts Create, update, and retrieve tracked prompts and their analytics, AI responses, domains, and URLs. Prompts are the AI search queries Topify.ai tracks daily across all AI providers. Each prompt generates responses that are analyzed for brand visibility, sentiment, citations, and competitor mentions. The API supports creating, updating, and retrieving prompts. Prompt deletion is intentionally not exposed through the public API. API keys are issued by team admins. ## List prompts ``` GET /projects/{project_id}/prompts ``` Returns all prompts in a project, ordered by creation date. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/prompts") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/prompts`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "project_id": "a1b2c3d4-...", "total": 25, "prompts": [ { "id": "e5f6a7b8-...", "project_id": "a1b2c3d4-...", "content": "What is the best project management tool?", "prompt_type": "active", "topic_id": "t1u2v3w4-...", "country": "US", "visibility": 85.0, "sentiment": 72, "position": 2.3, "volume": 150, "cvr": 45.0, "intent": "commercial", "trend": "up", "created_at": "2026-01-15T10:30:00+00:00" } ] } } ``` ### Prompt fields | Field | Type | Nullable | Description | | ------------- | ----------------- | -------- | ---------------------------------------------------------------------------- | | `id` | string (UUID) | No | Prompt ID | | `project_id` | string (UUID) | No | Parent project ID | | `content` | string | No | The AI search query text | | `prompt_type` | string | No | `active`, `suggested`, `pending`, `inactive`, or `url_recommended` | | `topic_id` | string (UUID) | Yes | Topic this prompt belongs to | | `country` | string | Yes | Target country for localized responses | | `visibility` | float | Yes | Brand visibility percentage (0--100) | | `sentiment` | integer | Yes | Brand sentiment score | | `position` | float | Yes | Average brand position in responses | | `volume` | integer | Yes | Number of responses collected | | `cvr` | float | Yes | Content visibility rate | | `intent` | string | Yes | Query intent: `informational`, `commercial`, `navigational`, `transactional` | | `trend` | string | Yes | Trend direction: `up`, `down`, or `stable` | | `created_at` | string (ISO 8601) | No | Creation timestamp | *** ## Generate suggested prompts ```http theme={null} POST /projects/{project_id}/prompts/suggested ``` Creates placeholder prompts immediately and fills their content through a background Semrush, DataForSEO, and LLM pipeline. Generation usually takes 30-90 seconds. ### Request body This generation endpoint currently uses camelCase request and response fields. | Field | Type | Required | Description | | ------------------ | ------- | -------- | -------------------------------------------------------------------- | | `count` | integer | No | Number to generate, from 1 to 50; defaults to the project batch size | | `generationMethod` | string | No | Currently only `keyword_seo_v1` | | `idempotencyKey` | string | No | Reuse the same value to retry safely | | `functionAnalysis` | string | No | Precomputed site analysis used to seed generation | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/suggested" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"count":10,"generationMethod":"keyword_seo_v1","idempotencyKey":"suggest-2026-07-13"}' ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "projectId": "a1b2c3d4-...", "queuedCount": 10, "createdCount": 10, "totalSuggested": 10, "prompts": [ { "promptId": "e5f6a7b8-...", "content": "", "promptType": "Suggested", "generationMethod": "keyword_seo_v1", "createdAt": "2026-07-13T12:00:00+00:00" } ], "message": "10 suggested prompt generation(s) queued." } } ``` Poll `GET /projects/{project_id}/prompts` and filter for `promptType=Suggested` to watch the placeholders fill in. *** ## Generate URL recommendations ```http theme={null} POST /projects/{project_id}/prompts/url-recommendations ``` Creates prompt recommendations for one to five target URLs. Existing-match checks and new candidate generation continue in the background after placeholders are returned. ### Request body | Field | Type | Required | Description | | ------- | --------- | -------- | -------------------------------------------------- | | `urls` | string\[] | Yes | One to five target URLs | | `count` | integer | No | Number of recommendations, from 1 to 20; default 5 | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/url-recommendations" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"urls":["https://acme.com/pricing"],"count":5}' ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "projectId": "a1b2c3d4-...", "urlsProvided": 1, "dbMatches": 0, "placeholdersCreated": 5, "message": "URL-recommendations generation started." } } ``` Poll the prompt list for `promptType=UrlRecommended`. Public prompt deletion and placeholder-cleanup operations are intentionally unavailable. *** ## Get prompt ``` GET /projects/{project_id}/prompts/{prompt_id} ``` Returns a single prompt. Same fields as list. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | | `prompt_id` | string (UUID) | The prompt ID | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/{prompt_id}" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/prompts/{prompt_id}") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/prompts/${promptId}`, { headers }); ``` *** ## Create prompts ``` POST /projects/{project_id}/prompts ``` Add one or more prompts to a project. Each prompt begins tracking immediately -- Topify.ai will start collecting AI responses and calculating metrics in the background. API keys are admin-created and currently have full-team access. Confirm the project and prompt text before creating prompts. ### Path parameters | Param | Type | Description | | ------------ | ------------- | ----------------------------- | | `project_id` | string (UUID) | The project to add prompts to | ### Request body | Field | Type | Required | Description | | --------- | ------------- | -------- | -------------------------------------------------- | | `prompts` | string\[] | Yes | Array of prompt texts to track (1--50 per request) | | `topicId` | string (UUID) | Yes | Topic to assign the prompts to | | `country` | string | No | ISO country code for localized responses | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "prompts": ["What is the best project management tool?", "Top PM software for teams"], "topicId": "t1u2v3w4-...", "country": "US" }' ``` ```python theme={null} resp = client.post(f"/projects/{project_id}/prompts", json={ "prompts": ["What is the best project management tool?"], "topicId": topic_id, "country": "US", }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/prompts`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ prompts: ["What is the best project management tool?"], topicId: topicId, country: "US", }), }); ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "projectId": "a1b2c3d4-...", "created": 2, "prompts": [ { "id": "e5f6a7b8-...", "projectId": "a1b2c3d4-...", "content": "What is the best project management tool?", "promptType": "Active", "topicId": "t1u2v3w4-...", "country": "US", "createdAt": "2026-03-30T10:30:00+00:00" } ] } } ``` ### Error responses | Status | Cause | | ------ | ------------------------------------------------------------------ | | `400` | Empty prompts array, whitespace-only content, or missing `topicId` | | `409` | A prompt with the same content already exists in this project | | `429` | Prompt quota exceeded for your plan tier | *** ## Update prompt ``` PATCH /projects/{project_id}/prompts/{prompt_id} ``` Update the content, country, topic, or type of an existing prompt. API keys are admin-created and currently have full-team access. Confirm the project and fields before updating a prompt. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------------- | | `project_id` | string (UUID) | The project ID | | `prompt_id` | string (UUID) | The prompt to update | ### Request body All fields are optional. Include only the fields you want to change. | Field | Type | Description | | ------------ | ------------- | ------------------------------------------------------ | | `content` | string | New prompt text (cannot be empty) | | `country` | string | ISO country code | | `topicId` | string (UUID) | Reassign to a different topic | | `promptType` | string | Change prompt type (`Active`, `Inactive`, `Suggested`) | ```bash theme={null} curl -X PATCH "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/{prompt_id}" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"content": "Best project management tools for remote teams"}' ``` ```python theme={null} resp = client.patch(f"/projects/{project_id}/prompts/{prompt_id}", json={ "content": "Best project management tools for remote teams", }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/prompts/${promptId}`, { method: "PATCH", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ content: "Best project management tools for remote teams" }), }); ``` ### Response Returns the updated prompt with all fields (same shape as [get prompt](#get-prompt)). ### Error responses | Status | Cause | | ------ | ----------------------------------------------------------------- | | `400` | Empty request body, whitespace-only content, or no fields changed | | `404` | Prompt not found in this project | *** ## Prompt analytics ``` GET /projects/{project_id}/prompts/{prompt_id}/analytics ``` Returns time-series visibility, position, and sentiment data grouped by date and AI provider. ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | ------- | ------------------------------- | | `duration_days` | integer | `7` | Days to look back | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `providers` | string | -- | Comma-separated provider filter | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/{prompt_id}/analytics?duration_days=7" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/prompts/{prompt_id}/analytics", params={"duration_days": 7}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/prompts/${promptId}/analytics?duration_days=7`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "prompt_id": "e5f6a7b8-...", "prompt_content": "What is the best project management tool?", "volume": 42, "sentiment": 72, "visibility_chart": { "2026-03-01": [ { "provider": "chatgpt", "value": 85.0 }, { "provider": "perplexity", "value": 90.0 } ], "2026-03-02": [ { "provider": "chatgpt", "value": 80.0 } ] }, "position_chart": { "2026-03-01": [ { "provider": "chatgpt", "value": 2.0 } ] }, "sentiment_chart": { "2026-03-01": [ { "provider": "chatgpt", "value": 75.0 } ] } } } ``` ### Response fields | Field | Type | Nullable | Description | | ------------------ | ------------- | -------- | ---------------------------------------------------- | | `prompt_id` | string (UUID) | No | Prompt ID | | `prompt_content` | string | No | The AI search query text | | `volume` | integer | Yes | Total responses collected | | `sentiment` | integer | Yes | Overall brand sentiment across the period | | `visibility_chart` | object | No | Date-keyed map of `ProviderMetric[]` (visibility %) | | `position_chart` | object | No | Date-keyed map of `ProviderMetric[]` (avg position) | | `sentiment_chart` | object | No | Date-keyed map of `ProviderMetric[]` (avg sentiment) | **ProviderMetric:** | Field | Type | Description | | ---------- | ------ | ------------------------------------------- | | `provider` | string | AI provider name (e.g., `chatgpt`) | | `value` | float | Metric value for that provider on that date | *** ## Prompt competitor metrics ``` GET /projects/{project_id}/prompts/{prompt_id}/competitors ``` Returns prompt-scoped metrics for every active competitor under a single prompt. Use this endpoint to export each brand's visibility, sentiment, position, and share of voice for one prompt without opening the dashboard prompt detail page. The response is computed from raw AI responses for the selected prompt and date range. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | | `prompt_id` | string (UUID) | The prompt ID | ### Query parameters | Param | Type | Default | Description | | --------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------- | | `duration_days` | integer | `30` | Days to look back when no explicit date range is provided | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `providers` | string | -- | Comma-separated provider filter | | `aggregate_over_time` | boolean | `true` | `true` returns one row per competitor for the whole period. `false` returns one row per competitor per day. | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/{prompt_id}/competitors?duration_days=7&aggregate_over_time=false" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get( f"/projects/{project_id}/prompts/{prompt_id}/competitors", params={"duration_days": 7, "aggregate_over_time": False}, ) ``` ```javascript theme={null} const resp = await fetch( `${BASE}/projects/${projectId}/prompts/${promptId}/competitors?duration_days=7&aggregate_over_time=false`, { headers } ); ``` ### Response ```json theme={null} { "success": true, "data": { "prompt_id": "e5f6a7b8-...", "prompt_content": "What are the best luxury cake brands for gifting online?", "period": { "date_from": "2026-03-01", "date_to": "2026-03-07" }, "aggregate_over_time": false, "granularity": "day", "items": [ { "date": "2026-03-01", "competitor_id": "00000000-0000-0000-0000-000000000000", "project_id": "a1b2c3d4-...", "name": "Acme Corp", "website": "https://acme.com", "icon_url": "https://logo.clearbit.com/acme.com", "icon_urls": [ "https://logo.clearbit.com/acme.com", "https://www.google.com/s2/favicons?domain=acme.com&sz=64" ], "state": "active", "is_own_brand": true, "visibility": 54.55, "sentiment": 76.9, "position": 2.83, "sov": 9.3, "mention_count": 12, "total_responses": 22, "total_brand_mentions": 129, "sentiment_count": 10, "position_count": 12, "days_with_data": 1, "days_with_responses": 1 } ], "total_items": 77, "total_competitors": 11 } } ``` ### PromptCompetitorsResponse fields | Field | Type | Nullable | Description | | --------------------- | ----------------------------- | -------- | --------------------------------------------------------------- | | `prompt_id` | string (UUID) | No | Prompt ID | | `prompt_content` | string | No | The AI search query text | | `period` | object | No | Date range used for the calculation | | `aggregate_over_time` | boolean | No | Whether rows are aggregated across the whole period | | `granularity` | string | No | `period` when aggregated, or `day` when daily rows are returned | | `items` | PromptCompetitorMetricItem\[] | No | Competitor metric rows | | `total_items` | integer | No | Number of rows in `items` | | `total_competitors` | integer | No | Number of active competitors included | ### PromptCompetitorMetricItem fields | Field | Type | Nullable | Description | | ---------------------- | ------------- | -------- | ------------------------------------------------------------------------ | | `date` | string | Yes | Date in `YYYY-MM-DD` format. `null` when `aggregate_over_time=true` | | `competitor_id` | string (UUID) | No | Competitor ID. `00000000-...` for injected own brand | | `project_id` | string (UUID) | No | Parent project ID | | `name` | string | No | Competitor brand name | | `website` | string | No | Competitor website URL | | `icon_url` | string | Yes | Primary logo URL | | `icon_urls` | string\[] | No | Multiple logo URL sources | | `state` | string | Yes | Competitor state, usually `active` | | `is_own_brand` | boolean | No | `true` if this row is your own brand | | `visibility` | float | Yes | Percentage of prompt responses that mention this competitor | | `sentiment` | float | Yes | Average sentiment score for this competitor | | `position` | float | Yes | Average ranked position when this competitor is mentioned | | `sov` | float | Yes | Share of voice: this competitor's mentions divided by all brand mentions | | `mention_count` | integer | No | Number of prompt responses where this competitor was mentioned | | `total_responses` | integer | No | Number of prompt responses in the row's date bucket or period | | `total_brand_mentions` | integer | No | Number of all brand mentions in the row's date bucket or period | | `sentiment_count` | integer | No | Number of sentiment observations used in `sentiment` | | `position_count` | integer | No | Number of position observations used in `position` | | `days_with_data` | integer | No | Number of days where this competitor was mentioned | | `days_with_responses` | integer | No | Number of days with prompt responses | With `aggregate_over_time=true`, `date` is `null` and each row covers the full `period`. With `aggregate_over_time=false`, `items` contains one row for each date and competitor. `visibility` is `0.0` when prompt responses exist but the competitor was not mentioned, and `null` when the prompt has no responses in that bucket. `sentiment`, `position`, and `sov` are `null` when there is no observation or denominator for that metric. *** ## AI responses (chats) ``` GET /projects/{project_id}/prompts/{prompt_id}/chats ``` Returns raw AI responses with brand mentions, sentiment, position, and citation references. ### Query parameters Same as [analytics](#prompt-analytics). ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/{prompt_id}/chats?duration_days=7" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/prompts/{prompt_id}/chats", params={"duration_days": 7}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/prompts/${promptId}/chats?duration_days=7`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "items": [ { "id": "c1d2e3f4-...", "date": "2026-03-01T12:00:00+00:00", "mentioned": true, "position": 2, "all_mentions": [ { "name": "Acme Corp", "icon_url": "https://logo.clearbit.com/acme.com", "icon_urls": ["https://logo.clearbit.com/acme.com", "https://www.google.com/s2/favicons?domain=acme.com&sz=64"] } ], "chat_preview": "When it comes to project management tools...", "platform": "chatgpt", "model": "gpt-4o", "full_content": "When it comes to project management tools, Acme Corp stands out for...", "prompt_content": "What is the best project management tool?", "references": [ { "title": "Best PM Tools 2026", "link": "https://techcrunch.com/best-pm-tools", "snippet": "Acme Corp ranked #1...", "reference_index": 1 } ], "brands_mentioned": { "Acme Corp": { "position": 1, "count": 3 }, "Competitor A": { "position": 2, "count": 1 } }, "sentiment": 80, "sentiment_by_brands": { "Acme Corp": 80, "Competitor A": 60 } } ] } } ``` ### ChatItem fields | Field | Type | Nullable | Description | | --------------------- | ----------------- | -------- | ------------------------------------------------------------------------------- | | `id` | string (UUID) | No | LLM output ID | | `date` | string (ISO 8601) | No | When the response was generated | | `mentioned` | boolean | No | Whether your brand was mentioned | | `position` | integer | Yes | Your brand's rank position in this response | | `all_mentions` | BrandMention\[] | No | All brands mentioned in the response | | `chat_preview` | string | No | First sentence of the response (max 150 chars) | | `platform` | string | No | AI provider (`chatgpt`, `perplexity`, `google_ai_overview`, `gemini`, `claude`) | | `model` | string | No | Specific model used (e.g., `gpt-4o`) | | `full_content` | string | No | Complete AI response text | | `prompt_content` | string | No | The prompt that generated this response | | `references` | ReferenceItem\[] | No | Citations/sources in the response | | `brands_mentioned` | object | Yes | Raw brand extraction data (brand name to position/count mapping) | | `sentiment` | integer | Yes | Your brand's sentiment score in this response | | `sentiment_by_brands` | object | Yes | Sentiment scores keyed by brand name | **BrandMention:** | Field | Type | Description | | ----------- | --------- | ----------------------------------------- | | `name` | string | Brand name as it appeared in the response | | `icon_url` | string | Primary logo URL | | `icon_urls` | string\[] | Multiple logo URL sources | **ReferenceItem:** | Field | Type | Description | | ----------------- | ------- | ---------------------------------------- | | `title` | string | Citation title | | `link` | string | Citation URL | | `snippet` | string | Citation snippet text | | `reference_index` | integer | Position of the citation in the response | *** ## Domains ``` GET /projects/{project_id}/prompts/{prompt_id}/domains ``` Returns domain-level citation breakdown, sorted by citation count descending. ### Query parameters Same as [analytics](#prompt-analytics). ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/{prompt_id}/domains?duration_days=7" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/prompts/{prompt_id}/domains", params={"duration_days": 7}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/prompts/${promptId}/domains?duration_days=7`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "items": [ { "domain": "techcrunch.com", "domain_type": 1, "citation_count": 15, "avg_citations": 2.5, "used_percentage": 75.0, "mentioned": true, "brands_appeared": [ { "name": "Acme Corp", "icon_url": "https://logo.clearbit.com/acme.com", "icon_urls": ["https://logo.clearbit.com/acme.com"] } ], "favicon_url": "https://www.google.com/s2/favicons?domain=techcrunch.com&sz=64", "favicon_urls": [ "https://logo.clearbit.com/techcrunch.com", "https://www.google.com/s2/favicons?domain=techcrunch.com&sz=64" ] } ] } } ``` ### DomainItem fields | Field | Type | Nullable | Description | | ----------------- | ------------ | -------- | ----------------------------------------------------- | | `domain` | string | No | Domain name | | `domain_type` | integer | No | Source category ID | | `citation_count` | integer | No | Total citation count across all responses | | `avg_citations` | float | No | Average citations per response that cited this domain | | `used_percentage` | float | No | How frequently this domain appears (percentage) | | `mentioned` | boolean | No | Whether your brand appeared alongside this domain | | `brands_appeared` | BrandInfo\[] | No | Brands that appeared in responses citing this domain | | `favicon_url` | string | Yes | Primary favicon URL | | `favicon_urls` | string\[] | No | Multiple favicon URL sources | **BrandInfo:** | Field | Type | Description | | ----------- | --------- | ------------------------- | | `name` | string | Brand name | | `icon_url` | string | Primary logo URL | | `icon_urls` | string\[] | Multiple logo URL sources | *** ## URLs ``` GET /projects/{project_id}/prompts/{prompt_id}/urls ``` Returns URL-level citation data, sorted by mention count descending. ### Query parameters Same as [analytics](#prompt-analytics). ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/prompts/{prompt_id}/urls?duration_days=7" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/prompts/{prompt_id}/urls", params={"duration_days": 7}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/prompts/${promptId}/urls?duration_days=7`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "items": [ { "url": "https://techcrunch.com/best-pm-tools-2026", "title": "Best PM Tools 2026", "domain": "techcrunch.com", "url_type": null, "url_type_name": "unknown", "mentioned_count": 8, "brands_appear": [ { "name": "Acme Corp", "icon_url": "https://logo.clearbit.com/acme.com", "icon_urls": ["https://logo.clearbit.com/acme.com"] } ], "first_seen": "2026-02-15T00:00:00+00:00", "last_seen": "2026-03-01T00:00:00+00:00" } ] } } ``` ### UrlItem fields | Field | Type | Nullable | Description | | ----------------- | ----------------- | -------- | ------------------------------------------------- | | `url` | string | No | Full URL | | `title` | string | No | Page title | | `domain` | string | No | Domain extracted from URL | | `url_type` | integer | Yes | URL type ID | | `url_type_name` | string | No | URL type label (e.g., `unknown`) | | `mentioned_count` | integer | No | Number of times this URL was cited | | `brands_appear` | BrandInfo\[] | No | Brands appearing in responses that cited this URL | | `first_seen` | string (ISO 8601) | No | Earliest citation date | | `last_seen` | string (ISO 8601) | No | Most recent citation date | # Recorded URLs Source: https://docs.topify.ai/api-reference/recordings Manage project URLs used as inputs for prompt discovery and recommendations. Recorded URLs are pages associated with a project that Topify can use for URL-driven prompt discovery. Recording a URL does not create a prompt by itself; use the prompt recommendation endpoint when you want new prompt candidates. ## List recorded URLs ```http theme={null} GET /projects/{project_id}/recording ``` ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/recording" \ -H "X-API-Key: tk_live_..." ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "project_id": "a1b2c3d4-...", "total": 1, "urls": [ { "id": "e5f6a7b8-...", "url": "https://acme.com/pricing", "normalized_url": "https://acme.com/pricing", "title": "Pricing | Acme", "icon_urls": ["https://www.google.com/s2/favicons?domain=acme.com&sz=128"], "created_at": "2026-07-13T12:00:00+00:00" } ] } } ``` *** ## Add recorded URLs ```http theme={null} POST /projects/{project_id}/recording ``` Adds up to 50 URLs in one request. If `title` is omitted, Topify fetches it in the background. ### Request body | Field | Type | Required | Description | | -------------- | --------- | -------- | ----------------------------------------------- | | `urls` | object\[] | Yes | One to 50 URL objects | | `urls[].url` | string | Yes | Page URL | | `urls[].title` | string | No | Page title; fetched asynchronously when omitted | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/recording" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"urls":[{"url":"https://acme.com/pricing","title":"Pricing | Acme"}]}' ``` ### Response ```json theme={null} { "code": 200, "message": "200 OK", "data": { "project_id": "a1b2c3d4-...", "affected_count": 1, "total_recorded": 4 } } ``` *** ## Remove recorded URLs ```http theme={null} DELETE /projects/{project_id}/recording ``` Removes URLs from the recording list. It does not delete prompts or prompt analytics. Send only the URLs you intend to remove. API keys currently have full-team access. ```bash theme={null} curl -X DELETE "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/recording" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{"urls":["https://acme.com/pricing"]}' ``` The response uses the same `affected_count` and `total_recorded` fields as the add endpoint. # Reports Source: https://docs.topify.ai/api-reference/reports Generate a self-contained HTML visibility report for a project. ## Generate report ```http theme={null} GET /projects/{project_id}/report ``` Returns a rendered HTML report instead of the standard JSON response envelope. ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | -------- | ---------------------------------- | | `duration_days` | integer | `30` | Lookback window from 1 to 365 days | | `date_from` | string | -- | Explicit start date (`YYYY-MM-DD`) | | `date_to` | string | -- | Explicit end date (`YYYY-MM-DD`) | | `format` | string | `inline` | `inline` or `download` | When `date_from` and `date_to` are provided, they take precedence over `duration_days`. ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/report?duration_days=30&format=download" \ -H "X-API-Key: tk_live_..." \ -o topify-report.html ``` For `format=download`, the response includes a `Content-Disposition` header when provided by the report service. ### Error responses | Status | Cause | | ------ | ------------------------------------------------- | | `403` | The API key's team cannot access the project | | `422` | Invalid date, duration, or format parameter | | `502` | The report backend is unavailable | | `504` | Report generation exceeded the 120-second timeout | # Sources Source: https://docs.topify.ai/api-reference/sources Retrieve aggregated source domains cited in AI responses. Sources are the domains that AI providers cite when answering tracked prompts. This endpoint aggregates citation data at the domain level across all prompts in a project. ## List sources ``` GET /projects/{project_id}/sources ``` Returns source domains with citation counts, paginated and sorted by `reference_count` descending. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | ------- | --------------------------------- | | `page` | integer | `1` | Page number (starts at 1) | | `page_size` | integer | `50` | Results per page (min 1, max 100) | | `duration_days` | integer | `7` | Days to look back | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `providers` | string | -- | Comma-separated provider filter | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/sources?page=1&page_size=50&duration_days=7" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/sources", params={"page": 1, "page_size": 50, "duration_days": 7}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/sources?page=1&page_size=50&duration_days=7`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": [ { "source_id": "s1t2u3v4-...", "domain": "techcrunch.com", "category": 1, "reference_count": 127 }, { "source_id": "w5x6y7z8-...", "domain": "forbes.com", "category": 1, "reference_count": 89 } ] } ``` ### SourceDomainResponse fields | Field | Type | Nullable | Description | | ----------------- | ------------- | -------- | ---------------------------------------------------- | | `source_id` | string (UUID) | No | Source ID | | `domain` | string | No | Domain name | | `category` | integer | No | Domain category ID | | `reference_count` | integer | No | Total citations across all prompts in the date range | The `data` field is a flat array (not wrapped in an object) for this endpoint. Pagination does not include a `total` count -- fetch pages until an empty array is returned. *** ## Get source detail ```http theme={null} GET /projects/{project_id}/sources/{domain}/detail ``` Returns citation statistics, top cited URLs, and prompts that cited one source domain. ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | ------- | ----------------------------------------------- | | `duration_days` | integer | `7` | Lookback window when explicit dates are omitted | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `providers` | string | -- | Comma-separated provider filter | | `page` | integer | `1` | Page for both URL and prompt lists | | `page_size` | integer | `20` | Items per list, maximum 100 | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/sources/techcrunch.com/detail?duration_days=30" \ -H "X-API-Key: tk_live_..." ``` The response includes `stats`, a paginated `urls` object, and a paginated `prompts` object. Each paginated object contains `page`, `page_size`, `total`, and `items`. *** ## List source chats ```http theme={null} GET /projects/{project_id}/sources/{domain}/chats ``` Returns AI responses that cited the requested domain. ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | ------- | ----------------------------------------------- | | `duration_days` | integer | `7` | Lookback window when explicit dates are omitted | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `providers` | string | -- | Comma-separated provider filter | | `page` | integer | `1` | Page number | | `page_size` | integer | `10` | Results per page, maximum 50 | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/sources/techcrunch.com/chats?providers=chatgpt&page=1&page_size=10" \ -H "X-API-Key: tk_live_..." ``` Each chat item includes the provider, model, prompt, preview, full response content, detected brand and sentiment data, and references. # Sources analytics Source: https://docs.topify.ai/api-reference/sources-analytics Rich analytics for source domains: category breakdown, performance trends, and opportunities. The sources analytics endpoint returns a comprehensive view of source domain activity -- aggregate stats, category breakdown, daily performance of top domains, and opportunity domains where your brand is not yet mentioned. ## Get sources analytics ``` GET /projects/{project_id}/sources/analytics ``` Returns source statistics, category distribution, a performance chart for the top 10 domains, the top 20 domains by citation count, and opportunity domains. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | ------- | ------------------------------- | | `duration_days` | integer | `7` | Days to look back | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `providers` | string | -- | Comma-separated provider filter | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/sources/analytics?duration_days=7" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/sources/analytics", params={"duration_days": 7}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/sources/analytics?duration_days=7`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "stats": { "total_sources": 245, "total_citations": 1832 }, "category_breakdown": { "corporate": 12.5, "editorial": 35.2, "institutional": 18.0, "reference": 22.1, "ugc": 8.7, "other": 3.5 }, "performance_chart": { "dates": ["2026-02-28", "2026-03-01", "2026-03-02"], "series": [ { "domain": "techcrunch.com", "values": [8.2, 7.5, 9.1] }, { "domain": "forbes.com", "values": [6.0, 5.8, 6.3] } ] }, "top_domains": [ { "source_id": "s1t2u3v4-...", "domain": "techcrunch.com", "category": 1, "reference_count": 127 }, { "source_id": "w5x6y7z8-...", "domain": "forbes.com", "category": 1, "reference_count": 89 } ], "opportunities": [ { "domain": "reddit.com", "citation_count": 45, "category": 4, "category_name": "ugc" } ] } } ``` ### SourcesAnalyticsResponse fields | Field | Type | Description | | -------------------- | ----------------------- | ------------------------------------------------------- | | `stats` | SourcesStats | Aggregate source and citation counts | | `category_breakdown` | CategoryBreakdown | Citation distribution by domain category (percentages) | | `performance_chart` | SourcePerformanceChart | Daily citation share for the top 10 domains | | `top_domains` | SourceDomainResponse\[] | Top 20 domains by citation count | | `opportunities` | SourceOpportunity\[] | High-citation domains where your brand is not mentioned | ### SourcesStats fields | Field | Type | Description | | ----------------- | ------- | ----------------------------------------------- | | `total_sources` | integer | Distinct source domains cited in the date range | | `total_citations` | integer | Total citation count across all sources | ### CategoryBreakdown fields Each field is a percentage (0--100) of total citations belonging to that category. | Field | Category ID | Description | | --------------- | ----------- | --------------------------------------------------- | | `corporate` | 0 | Company and brand websites | | `editorial` | 1 | News outlets and media publications | | `institutional` | 2 | Government, academic, and non-profit sites | | `reference` | 3 | Encyclopedias, documentation, and knowledge bases | | `ugc` | 4 | User-generated content (forums, social media, Q\&A) | | `other` | 5 | Uncategorized domains | ### SourcePerformanceChart fields | Field | Type | Description | | -------- | -------------------- | ------------------------------------------ | | `dates` | string\[] | Sorted date strings in `YYYY-MM-DD` format | | `series` | DomainChartSeries\[] | One entry per top-10 domain | ### DomainChartSeries fields | Field | Type | Description | | -------- | -------- | ---------------------------------------------------------------------------------- | | `domain` | string | Domain name | | `values` | float\[] | Daily citation share as a percentage of all citations that day, aligned to `dates` | ### SourceDomainResponse fields | Field | Type | Description | | ----------------- | ------------- | ------------------------------------------ | | `source_id` | string (UUID) | Source ID | | `domain` | string | Domain name | | `category` | integer | Domain category ID (see CategoryBreakdown) | | `reference_count` | integer | Total citations in the date range | ### SourceOpportunity fields | Field | Type | Description | | ---------------- | ------- | -------------------------------------------------- | | `domain` | string | Domain name | | `citation_count` | integer | Total citations where your brand was not mentioned | | `category` | integer | Domain category ID | | `category_name` | string | Human-readable category name | Opportunities are domains cited 3 or more times where your brand is either not mentioned or has `mentioned = false`. These represent sources you could target for brand placement to increase visibility. ### Use cases Use `category_breakdown` to understand which types of sources AI providers cite most. A high `ugc` percentage may indicate opportunities to improve presence on community platforms. Plot `performance_chart` as a stacked area or line chart to see how the citation share of top domains shifts over time. The `opportunities` array surfaces high-traffic sources where your brand is absent. Prioritize outreach to these domains to improve your visibility in AI responses. # Topics Source: https://docs.topify.ai/api-reference/topics Retrieve topic groups used to organize prompts. Topics are groups that organize your prompts by theme or category (e.g., "Product comparisons", "Pricing questions"). Each prompt belongs to one topic. ## List topics ``` GET /projects/{project_id}/topics ``` Returns all topics in a project. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Query parameters None. ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/topics" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/topics") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/topics`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": [ { "topic_id": "t1u2v3w4-...", "project_id": "a1b2c3d4-...", "name": "Product comparisons", "description": "Prompts comparing products in the category", "created_at": "2026-01-20T14:00:00+00:00" }, { "topic_id": "u5v6w7x8-...", "project_id": "a1b2c3d4-...", "name": "Pricing questions", "description": null, "created_at": "2026-01-20T14:05:00+00:00" } ] } ``` ### TopicResponse fields | Field | Type | Nullable | Description | | ------------- | ----------------- | -------- | ------------------ | | `topic_id` | string (UUID) | No | Topic ID | | `project_id` | string (UUID) | No | Parent project ID | | `name` | string | No | Topic display name | | `description` | string | Yes | Topic description | | `created_at` | string (ISO 8601) | No | Creation timestamp | Use the `topic_id` to correlate prompts to their topic group via the `topic_id` field in the [prompts](/api-reference/prompts) and [overview](/api-reference/overview) endpoints. # Visibility trends Source: https://docs.topify.ai/api-reference/visibility Time-series visibility, sentiment, and position data per competitor. The visibility trends endpoint returns daily time-series metrics for every active competitor in a project. Use it to chart how brand visibility, sentiment, and position change over time across AI providers. ## Get visibility trends ``` GET /projects/{project_id}/visibility ``` Returns daily visibility, sentiment, and position values for each active competitor, averaged across selected providers. ### Path parameters | Param | Type | Description | | ------------ | ------------- | -------------- | | `project_id` | string (UUID) | The project ID | ### Query parameters | Param | Type | Default | Description | | --------------- | ------- | ------- | ------------------------------- | | `duration_days` | integer | `7` | Days to look back | | `date_from` | string | -- | Start date (`YYYY-MM-DD`) | | `date_to` | string | -- | End date (`YYYY-MM-DD`) | | `providers` | string | -- | Comma-separated provider filter | ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/visibility?duration_days=7" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get(f"/projects/{project_id}/visibility", params={"duration_days": 7}) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/projects/${projectId}/visibility?duration_days=7`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": { "dates": ["2026-02-28", "2026-03-01", "2026-03-02", "2026-03-03"], "period": "2026-02-28 to 2026-03-03", "series": [ { "competitor_id": "00000000-0000-0000-0000-000000000000", "name": "Acme Corp", "website": "https://acme.com", "icon_url": "https://logo.clearbit.com/acme.com", "is_own_brand": true, "visibility": [82.5, 85.0, 83.2, 86.1], "sentiment": [70.0, 72.0, 71.5, 73.0], "position": [1.8, 1.6, 1.9, 1.5] }, { "competitor_id": "c1d2e3f4-...", "name": "Competitor A", "website": "https://competitor-a.com", "icon_url": "https://logo.clearbit.com/competitor-a.com", "is_own_brand": false, "visibility": [60.0, 62.5, null, 65.0], "sentiment": [55.0, 58.0, null, 60.0], "position": [3.2, 3.0, null, 2.8] } ] } } ``` ### VisibilityTrendsResponse fields | Field | Type | Description | | -------- | ------------------- | ------------------------------------------------------------ | | `dates` | string\[] | Sorted date strings in `YYYY-MM-DD` format | | `period` | string | Human-readable date range (e.g., `2026-02-28 to 2026-03-03`) | | `series` | CompetitorSeries\[] | One entry per active competitor | ### CompetitorSeries fields | Field | Type | Nullable | Description | | --------------- | ------------- | -------- | ------------------------------------------------------------------------ | | `competitor_id` | string (UUID) | No | Competitor ID | | `name` | string | No | Competitor brand name | | `website` | string | No | Competitor website URL | | `icon_url` | string | Yes | Primary logo URL | | `is_own_brand` | boolean | No | `true` if this is your own brand | | `visibility` | float\[] | No | Daily visibility scores aligned to `dates`. `null` for days with no data | | `sentiment` | float\[] | No | Daily sentiment scores aligned to `dates`. `null` for days with no data | | `position` | float\[] | No | Daily position values aligned to `dates`. `null` for days with no data | When multiple providers have data for the same day, values are averaged. A `null` entry means no data was collected for that competitor on that date. ### Use cases Plot each competitor as a line on a time-series chart. The `dates` array provides x-axis labels and each series' `visibility`, `sentiment`, and `position` arrays provide y-axis values. Use `is_own_brand` to highlight your brand against competitors. Compare trends to see if your visibility is growing faster or slower than the competition. Pass `?providers=chatgpt` to isolate trends for a single AI provider and compare performance across platforms. # Webhooks Source: https://docs.topify.ai/api-reference/webhooks Receive real-time notifications when action workflows reach checkpoints or complete. Webhooks let you receive real-time notifications when action workflows reach checkpoints or complete. They are required for the [execute workflow](/api-reference/actions#execute-action) used by `new_content` actions. ## Register webhook ``` POST /webhooks ``` Register a new webhook endpoint to receive event notifications. ### Request body | Field | Type | Required | Description | | -------- | --------- | -------- | --------------------------------------------------- | | `url` | string | Yes | HTTPS endpoint URL that will receive webhook events | | `events` | string\[] | Yes | Array of event types to subscribe to | ### Valid events | Event | Description | | ------------------- | --------------------------------------------------------------------------- | | `action.checkpoint` | Workflow paused at an approval checkpoint (outline, draft, or final review) | | `action.completed` | Workflow finished successfully | | `action.failed` | Workflow encountered an error | ```bash theme={null} curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/webhooks" \ -H "X-API-Key: tk_live_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhooks/topify", "events": ["action.checkpoint", "action.completed", "action.failed"] }' ``` ```python theme={null} resp = client.post("/webhooks", json={ "url": "https://example.com/webhooks/topify", "events": ["action.checkpoint", "action.completed", "action.failed"], }) ``` ```javascript theme={null} const resp = await fetch(`${BASE}/webhooks`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ url: "https://example.com/webhooks/topify", events: ["action.checkpoint", "action.completed", "action.failed"], }), }); ``` ### Response (201 Created) ```json theme={null} { "success": true, "data": { "id": "wh_a1b2c3d4-...", "url": "https://example.com/webhooks/topify", "events": ["action.checkpoint", "action.completed", "action.failed"], "is_active": true, "secret": "whsec_k7m9p2...", "created_at": "2026-04-08T12:00:00Z" } } ``` The `secret` field is only returned once at creation time. Store it securely -- you will need it to verify webhook signatures. ### Error responses | Status | Cause | | ------ | ------------------------------------------------------------------------- | | `400` | Missing `url`, non-HTTPS URL, empty `events` array, or invalid event type | | `409` | A webhook with this URL already exists | *** ## List webhooks ``` GET /webhooks ``` Returns all registered webhooks for your account. ```bash theme={null} curl "https://topify-customer-api-production.up.railway.app/api/public/v1/webhooks" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.get("/webhooks") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/webhooks`, { headers }); ``` ### Response ```json theme={null} { "success": true, "data": [ { "id": "wh_a1b2c3d4-...", "url": "https://example.com/webhooks/topify", "events": ["action.checkpoint", "action.completed", "action.failed"], "is_active": true, "created_at": "2026-04-08T12:00:00Z" } ] } ``` The `secret` field is not included in list responses. It is only returned when the webhook is first created. *** ## Delete webhook ``` DELETE /webhooks/{webhook_id} ``` Permanently remove a webhook. Events will no longer be delivered to the endpoint. Confirm the webhook ID and destination before deleting it. Deletion stops event delivery immediately, and registering the URL again creates a new signing secret. ### Path parameters | Param | Type | Description | | ------------ | ------ | -------------- | | `webhook_id` | string | The webhook ID | ```bash theme={null} curl -X DELETE "https://topify-customer-api-production.up.railway.app/api/public/v1/webhooks/{webhook_id}" \ -H "X-API-Key: tk_live_..." ``` ```python theme={null} resp = client.delete(f"/webhooks/{webhook_id}") ``` ```javascript theme={null} const resp = await fetch(`${BASE}/webhooks/${webhookId}`, { method: "DELETE", headers, }); ``` ### Response ```json theme={null} { "success": true, "data": { "id": "wh_a1b2c3d4-...", "message": "Webhook deleted successfully" } } ``` ### Error responses | Status | Cause | | ------ | ----------------- | | `404` | Webhook not found | *** ## Webhook payload format When an event fires, Topify sends a POST request to your registered URL with the following JSON body. ### `action.checkpoint` payload ```json theme={null} { "event": "action.checkpoint", "timestamp": "2026-04-08T21:19:13.463Z", "data": { "action_id": "a1b2c3d4-...", "workflow_id": "w1x2y3z4-...", "checkpoint": "outline", "content": "## Article Outline\n\n1. Introduction to project management tools...", "options": [ { "action": "approve", "label": "Approve & Write" }, { "action": "edit", "label": "Edit Outline" }, { "action": "regenerate", "label": "Regenerate" }, { "action": "reject", "label": "Cancel" } ] } } ``` ### `action.completed` payload ```json theme={null} { "event": "action.completed", "timestamp": "2026-04-08T21:25:00.000Z", "data": { "action_id": "a1b2c3d4-...", "workflow_id": "w1x2y3z4-...", "result": { "title": "Best Project Management Tools in 2026", "content": "..." } } } ``` ### `action.failed` payload ```json theme={null} { "event": "action.failed", "timestamp": "2026-04-08T21:22:00.000Z", "data": { "action_id": "a1b2c3d4-...", "workflow_id": "w1x2y3z4-...", "error": "LLM generation timed out after 120 seconds" } } ``` ### Payload fields | Field | Type | Description | | ------------------ | ------------- | --------------------------------------------------------------------- | | `event` | string | Event type (`action.checkpoint`, `action.completed`, `action.failed`) | | `timestamp` | string | ISO 8601 timestamp of when the event occurred | | `data.action_id` | string (UUID) | The action that triggered the event | | `data.workflow_id` | string (UUID) | The active workflow ID | | `data.checkpoint` | string | Checkpoint name (only for `action.checkpoint`) | | `data.content` | string | Content to review (only for `action.checkpoint`) | | `data.options` | object\[] | Available response actions (only for `action.checkpoint`) | | `data.result` | object | Final output (only for `action.completed`) | | `data.error` | string | Error message (only for `action.failed`) | *** ## Verifying signatures Every webhook request includes an `X-Webhook-Signature` header containing an HMAC-SHA256 signature of the exact raw request body, computed using the webhook secret returned at creation time. Always verify this signature with a constant-time comparison before parsing or processing the payload. Reject requests with a missing or invalid signature, and make handlers idempotent because delivery may be retried. ```python theme={null} import hmac import hashlib def verify_webhook(body: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), body, hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, signature) # In your webhook handler: signature = request.headers["X-Webhook-Signature"] is_valid = verify_webhook(request.body, signature, WEBHOOK_SECRET) ``` ```javascript theme={null} import { createHmac, timingSafeEqual } from "crypto"; function verifyWebhook(body, signature, secret) { const expected = Buffer.from(createHmac("sha256", secret) .update(body) .digest("hex"), "utf8"); const received = Buffer.from(signature || "", "utf8"); return expected.length === received.length && timingSafeEqual(expected, received); } // In your webhook handler: const signature = req.headers["x-webhook-signature"]; const isValid = verifyWebhook(req.rawBody, signature, WEBHOOK_SECRET); ``` Your endpoint should return a `200` status code within 10 seconds. If your endpoint fails to respond or returns a non-2xx status, Topify will retry up to 3 times with exponential backoff. # Documentation launch Source: https://docs.topify.ai/changelog/2026-02-20 Initial release of the Topify.ai documentation site. Launched the Topify.ai documentation site with comprehensive coverage of the platform: * **Getting started** -- introduction to GEO and a quickstart guide for new users * **Concepts** -- terminology, brand metrics, source metrics, and GEO methodology * **Platform guides** -- step-by-step walkthroughs for the dashboard, prompts, competitors, sources, content generation, and the AI agent * **Platform reference** -- billing and plans, team management, and integrations * **API reference** -- endpoint documentation for the data analysis and content generation APIs * **Support** -- FAQ and troubleshooting guides # Public API: prompt create and update endpoints Source: https://docs.topify.ai/changelog/2026-03-30 Create and update prompts through the public API. ## Prompt write endpoints The public API now supports creating and updating prompts. Previously, the API was read-only for prompt data. ### New endpoints * **POST** `/projects/{project_id}/prompts` -- create one or more prompts (up to 50 per request) * **PATCH** `/projects/{project_id}/prompts/{prompt_id}` -- update prompt content, country, topic, or type * Prompt deletion is intentionally not exposed through the public API because it is irreversible and unsafe for unattended automation. API keys can only be created by team administrators. Current keys have full access to all projects owned by the team; project-scoped keys are not available. Existing read-only endpoints are unchanged. See the [prompts reference](/api-reference/prompts) for full request and response documentation. ### API key scope API keys are team-wide credentials. Confirm the target `project_id` before every write operation and keep keys in trusted server-side environments only. ### API key visibility You can now view and copy full API keys from the **Management > Members** page in the dashboard. Keys created before this update only display a prefix. # Brand metrics Source: https://docs.topify.ai/concepts/brand-metrics How visibility, sentiment, position, and volume are calculated for your brand across AI search engines. ## Visibility: how often your brand appears **Definition**: Measures how often your brand appears in AI-generated responses. Higher visibility indicates stronger brand presence across AI platforms. ### Formula ``` Visibility (%) = (Responses mentioning your brand / Total responses) x 100 ``` A brand is considered "mentioned" if it appears in the extracted brands list for that response. ### Per-prompt example ``` Prompt: "Which MOBA games are best for beginners?" Time period: Last 7 days Your brand: League of Legends Data: - Total responses for this prompt: 10 - Responses mentioning League of Legends: 8 Visibility = (8 / 10) x 100 = 80% ``` League of Legends appeared in 80% of AI responses to this prompt. ### Aggregated example When viewing the overview dashboard, visibility is calculated across **all prompts** in your project. ``` Project: League of Legends Time period: Last 7 days Data: - Prompt 1: 8/10 mentions - Prompt 2: 12/15 mentions - Prompt 3: 7/12 mentions - Total responses: 37 - Total mentioning League of Legends: 27 Visibility = (27 / 37) x 100 = 73% ``` League of Legends appeared in 73% of all AI responses across all tracked prompts. *** ## Sentiment: how positively AI describes you **Definition**: Reflects the positivity of your brand's perception in AI-generated content. Higher scores indicate more favorable mentions. ### How sentiment works * Each response is analyzed to determine the overall tone toward each mentioned brand * One sentiment score (0--100) is assigned **per brand per response**, regardless of how many times the brand is mentioned * The score reflects the overall context and portrayal of the brand in the entire response ### Scale | Score | Interpretation | | ------ | ------------------------- | | 0 | Completely negative | | 50 | Neutral | | 100 | Completely positive | | 50--70 | Typical range in practice | Sentiment is only calculated for responses where your brand is **visible** (mentioned). If your brand doesn't appear in a response, no sentiment score is generated for that response. ### Per-prompt example ``` Prompt: "Which MOBA games are best for beginners?" Your brand: League of Legends Response 1: "League of Legends is excellent for beginners..." -> Sentiment: 85 Response 2: "League of Legends has a steep learning curve but good resources..." -> Sentiment: 78 Response 3: "For new players, League of Legends offers excellent support..." -> Sentiment: 82 Response 4: "League of Legends is highly recommended for beginners..." -> Sentiment: 88 Average sentiment = (85 + 78 + 82 + 88) / 4 = 83.25 ``` Even though "League of Legends" may appear multiple times in each response, only one sentiment score is assigned per response based on the overall tone. ### Aggregated example ``` Project: League of Legends Time period: Last 7 days Prompt 1 sentiments: [85, 78, 82, 88] Prompt 2 sentiments: [90, 87, 85, 92, 88] Prompt 3 sentiments: [75, 80, 78] All scores: [85, 78, 82, 88, 90, 87, 85, 92, 88, 75, 80, 78] Total responses with sentiment: 12 Average = 1008 / 12 = 84 ``` An overall sentiment of 84 indicates consistently positive brand perception across all prompts. *** ## Position: where you appear in the response **Definition**: How early your brand is named in AI responses, relative to the other brands in the same response. Being named first usually means more buyer recall. ### How position works For each AI response that mentions your brand: 1. Find every brand's **first** mention (the character offset of where it first appears). 2. Sort those brands by that offset, earliest first. 3. Your brand's **rank in that response** = its index + 1 (1 = earliest, 2 = second-named, and so on). Your per-prompt or per-project position is the **average of those ranks** across responses where you appear. Character offsets are computed internally; what you see on the dashboard is the rank. Lower rank = better. A position of 1 means you're consistently the first brand named. A position of 4 means three other brands are typically named before you. ### Ranking | Position | Meaning | | -------- | ------------------------------------------------ | | 1 | You're the first brand named in every response | | 2 | One other brand is named ahead of you on average | | 3+ | Two or more brands are named ahead, etc. | | 0 | No position data — you weren't mentioned | ### Worked example ``` Prompt: "Which MOBA games are best for beginners?" Your brand: League of Legends Response 1, brands in order of first mention: Dota 2 (1st) → Heroes of the Storm (2nd) → League of Legends (3rd) → League of Legends rank: 3 Response 2: League of Legends (1st) → Mobile Legends (2nd) → Dota 2 (3rd) → League of Legends rank: 1 Response 3: Heroes of the Storm (1st) → Dota 2 (2nd) → League of Legends (3rd) → League of Legends rank: 3 Average rank = (3 + 1 + 3) / 3 = 2.33 ``` League of Legends has an average position of **2.33** — typically the second or third brand named. *** ## Share of voice (SOV): your slice of the brand-mention slots **Definition**: The share of all brand-mention slots in AI answers that go to your brand. Where visibility asks *"did you appear at all?"*, SOV asks *"how much of the brand-naming real estate did you take vs everyone else?"* ### Formula For each response, Topify extracts a **list of distinct brands** that were named (the `brands_mentioned` field). SOV counts how many entries in that list across all responses belong to your brand vs everyone's brands. ``` SOV (%) = (Your-brand entries across all responses / All-brand entries across all responses) x 100 ``` * **Denominator**: every (response, distinct brand) pair. A response that names 5 different brands contributes 5; a response that names 0 contributes 0; repeat text occurrences of the same brand within one response count as 1. * **Numerator**: the subset of those entries that are your brand — effectively 1 per response your brand appears in (because the list is keyed by distinct brand). ### Worked example ``` Prompt: "Which MOBA games are best for beginners?" Your brand: League of Legends Time period: Last 7 days 10 responses analyzed. Each response's brands_mentioned list: Response 1: [LoL, Dota 2, Smite] → 3 entries Response 2: [LoL, Dota 2] → 2 Response 3: [Heroes of the Storm, Dota 2] → 2 Response 4: [LoL, Mobile Legends, Dota 2, Smite] → 4 Responses 5-10: similar mix ────── All-brand entries across all 10 responses: 30 Your-brand entries (LoL appears in 7 responses): 7 SOV = (7 / 30) x 100 = 23.3% ``` LoL takes **23.3%** of all brand-mention slots in AI answers for this prompt. Visibility for the same prompt is 70% (named in 7/10 responses), but SOV is lower because competitors share the remaining slots. ### Visibility vs SOV The two metrics together tell you not just whether you appear, but how crowded the answers are when you do. | Visibility | SOV | What it means | | ---------- | ---- | --------------------------------------------------------------------- | | High | High | You dominate — named often, with few competitors named alongside | | High | Low | Named often, but in crowded answers (AI lists many brands every time) | | Low | High | Named rarely, but when named, the answer is mostly about you | | Low | Low | Effectively absent | *** ## Volume: estimated AI search demand **Definition**: An estimate of how much search demand exists for a given prompt across AI platforms. Volume is **not** a count of responses collected by Topify.ai. It reflects how frequently people are likely asking similar questions to AI tools. Use it as a relative benchmark to compare prompts and prioritize which topics drive the most AI search traffic. The exact formula is proprietary. ### Example ``` Prompt: "Best project management tools for remote teams" Estimated AI volume = 2,400 Prompt: "Best mouse for Toyota AirPods" Estimated AI volume ≈ 0 ``` The first prompt reflects a real question people ask AI tools. The second combines unrelated topics, so despite containing popular individual words, the estimated demand is near zero. *** ## Mentions: which brands appear together A structured list of your tracked competitors that actually appeared in AI responses during the selected time period. Unlike the numeric metrics above, mentions show **which** competitors appeared and **how often**. ``` Tracked competitors: League of Legends, Dota 2, Heroes of the Storm, Mobile Legends, Brawl Stars, Smite Mentions (only those that appeared): - League of Legends: 8 mentions - Dota 2: 6 mentions - Heroes of the Storm: 5 mentions - Mobile Legends: 3 mentions - Brawl Stars: 2 mentions Smite is tracked but did not appear in any responses during this period. ``` *** ## FAQ ### What's a "good" visibility number? * **Above \~50%** — very strong; you're named in more than half of AI answers for your tracked prompts. * **20–50%** — healthy. * **Below 20%** — you're not yet a default answer in your category. This is where optimization work pays off most. ### Why is my visibility 0%? Most common causes: * **Prompt-topic mismatch** — your tracked prompts don't match the topics where your brand actually has online presence (e.g. you ship product A but the prompts ask about category B). * **Brand too new or niche** — AI models haven't picked you up yet. * **Name collision** — your brand name overlaps with a common word, so mentions get attributed elsewhere. * **Not enough data yet** — first hours after bootstrap. Fix: widen your prompts to match your real online footprint. See [Troubleshooting](/support/troubleshooting) for setup-side causes. ### Why are sentiment and position blank for some prompts? Both metrics are only computed when your brand actually appears in the response. If visibility is 0 for a prompt, there's no mention to score from — both come back blank. ### Why do my numbers differ across AI providers? Each provider uses a different model, training data, and citation style, so the same question produces different answers across them. This is expected — the per-provider breakdown is one of the most useful views, because the provider you score lowest on is usually your highest-leverage place to focus. # GEO methodology Source: https://docs.topify.ai/concepts/geo-methodology The research-backed techniques Topify.ai uses to optimize content for AI search engines. ## What is GEO? **Generative Engine Optimization (GEO)** is a set of techniques designed to improve how your content appears in AI-powered search engines like ChatGPT, Perplexity, Google AI Overview, Gemini, and Claude. Traditional SEO optimizes for Google's ranking algorithm. GEO optimizes for how large language models select, cite, and present information. The two disciplines overlap but have distinct techniques and goals. ## SEO vs GEO: what's the difference? | Aspect | Traditional SEO | GEO | | ------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------------- | | **Target** | Search engine result pages (Google, Bing) | AI-generated responses (ChatGPT, Perplexity, Google AI Overview, Gemini, Claude) | | **Ranking signal** | Backlinks, keywords, page authority | Content quality, citability, factual depth | | **Success metric** | Page rank, click-through rate | Brand visibility, sentiment, citation rate | | **Content format** | Optimized for crawlers and snippets | Optimized for AI comprehension and citation | | **Overlap** | Both benefit from high-quality, authoritative content | | ## Research behind GEO Topify.ai's GEO methodology is based on research published by Princeton University (KDD 2024). The study tested specific content optimization techniques across thousands of AI-generated responses and measured their impact on brand visibility. ## The 8 GEO optimization techniques Each technique has been measured for its impact on visibility in AI search results. | Technique | Visibility boost | Description | | ---------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | **Cite sources** | +40% | Add 2--3 authoritative citations per major claim. Reference academic papers, industry reports, or established publications. | | **Statistics** | +37% | Replace vague claims with specific numbers. Use percentages, quantities, and timeframes instead of words like "many" or "significant." | | **Quotations** | +30% | Include expert quotes with full attribution (name, title, organization). Quotes should directly support key arguments. | | **Authoritative tone** | +25% | Use confident, declarative statements instead of hedging language. Lead with conclusions, then provide evidence. | | **Easy language** | +20% | Simplify technical concepts using analogies and plain-English summaries. Make content accessible to non-specialists. | | **Technical terms** | +18% | Incorporate domain-specific terminology with inline definitions. This signals expertise to AI systems. | | **Unique words** | +15% | Increase vocabulary variety throughout content. Replace repeated words with synonyms to improve lexical diversity. | | **Fluency** | +15--30% | Optimize sentence flow with smooth transitions, clear paragraph structure, and an "answer-first" format. | These boost percentages represent average improvements measured across the research. Actual results vary depending on your industry, content quality, and competitive landscape. ## Techniques by industry Different industries benefit from different technique combinations. The most effective methods depend on what AI models prioritize for your domain. | Industry | Recommended techniques | | -------------- | ------------------------------------------------------ | | **Technology** | Technical terms, cite sources, statistics, fluency | | **Business** | Statistics, authoritative tone, cite sources, fluency | | **Healthcare** | Easy language, statistics, cite sources, fluency | | **Legal** | Cite sources, quotations, authoritative tone, fluency | | **Education** | Easy language, unique words, fluency, cite sources | | **E-commerce** | Statistics, authoritative tone, easy language, fluency | ## How Topify helps you apply this You don't need to rewrite every page yourself. Topify applies these techniques in three places: * **Content generation** — articles produced through **Optimization > Actions** are written with the relevant techniques baked in by default. Citations, statistics, and an authoritative tone come standard. * **AI agent ([`geo_optimize` skill](/guides/using-ai-agent))** — pass in an existing URL or block of text and the agent rewrites it to apply the techniques most relevant for your industry, returning the optimized version with a changelog of what changed and an estimated visibility boost. * **Sources page** — shows which domains AI currently trusts as sources for your prompts. Use it to find pages whose techniques are working, and where the gaps are for you. The shortest path: optimize your top 5–10 highest-traffic pages with `geo_optimize`, then start generating new articles on the topics where competitor domains currently dominate the Sources page. ## Channel mix for GEO Your owned site is the **only AI-era brand asset you fully control** — and it's what determines how AI assistants understand your brand. Understanding is the precondition for being recommended. Topify treats your blog as the foundation, with other channels playing supporting roles: | Channel | Role | | ------------------------------ | -------------------------------------------------------------- | | **Owned blog** | Authoritative content source + conversion entry point | | **Industry media / backlinks** | Boost domain authority and earn third-party citations | | **LinkedIn** | Reach industry practitioners and purchasing decision-makers | | **Reddit** | Surface in community discussions and pick up real-user signals | | **Medium** | Extend distribution and broaden AI visibility | Expect **30–60 days** for GEO work to translate into a noticeable lift in organic traffic. AI providers re-crawl on their own schedule, and citation patterns shift gradually as your new content gets picked up. Treat publishing as compounding investment, not a one-shot lever. ## FAQ ### How is "GEO content" different from a normal blog post? A normal blog post is written for human readers and search engines — keyword-targeted headings, narrative flow, hopefully some links. A GEO-optimized post layers in the things AI assistants actively look for when deciding what to quote: clear factual claims, named statistics with sources, expert quotations with attribution, and a confident, declarative tone. Both can coexist on the same page — GEO is more about the *additions* than a different writing style. # How Topify Works Source: https://docs.topify.ai/concepts/how-topify-works The pipeline behind Topify: how prompts get sent, how AI responses are collected and scored, and how competitors are auto-detected. This page walks through what happens after you create a project — from the daily prompt run to the metrics on your dashboard. ## Daily prompt run Topify sends every active prompt in your project to all monitored AI providers, once per day. Each response is collected within hours of the run, then handed off to analysis. For the full provider list and which platforms count toward your plan, see [Integrations](/platform/integrations). ## What gets analyzed Every response is parsed for four things: * **Brand mentions** — which brands appear, used to compute visibility and position * **Sentiment** — how positively each mentioned brand is described (0–100) * **Source citations** — which websites and URLs the AI references * **Competitor signal** — brands that consistently appear alongside yours For metric definitions and formulas, see [Brand metrics](/concepts/brand-metrics) and [Source metrics](/concepts/source-metrics). ## Data retention Every response is preserved indefinitely. The dashboard exposes 7-, 14-, 30-, and 90-day query windows for trend analysis, but no data is dropped after those windows. ## Bootstrap: prompts and competitors When you create a project, Topify runs a one-time setup pass: * **Prompt generation** — your website is crawled to understand your industry and offerings, and a starter set of prompts is drafted across the four intent types (informational, comparison, transactional, navigational). * **Competitor detection** — your starter prompts are sent to AI providers, and brands that consistently appear in the responses are flagged as competitors. This captures your **AI search competitors**, which are often different from your traditional market competitors. Both are reviewable: you decide which suggested prompts to activate and which competitors to track. ## FAQ ### Can I track competitors that AI doesn't surface yet? Yes. From **Preferences > Competitors**, manually add any brand by name and Topify starts watching for it in every response we collect going forward. This is useful for emerging players or adjacent-category brands you want to monitor before they become AI-visible. ### How does Topify get the AI responses — through official APIs? We collect responses by driving the actual AI assistant interfaces (ChatGPT, Perplexity, Google AI Overview, and Gemini) — automated browser sessions against the same web UIs your buyers use. This means the data reflects what real users actually see, not a stripped-down API view that may differ from the consumer experience. **Claude is the exception** — we use Anthropic's official API, since the consumer Claude product doesn't differ meaningfully from the API output. # Source metrics Source: https://docs.topify.ai/concepts/source-metrics Definitions and formulas for citation count, used %, avg citations, and source authority — computed at both domain and URL granularity. ## Overview Source metrics track how AI models use and cite content sources when generating responses. These metrics reveal which domains have **content authority** -- the ability to influence what AI search engines say. Sources are analyzed at two levels: * **Domain level** -- aggregated across all URLs from a domain (e.g., all pages on `techcrunch.com`) * **URL level** -- individual page performance (e.g., `techcrunch.com/article-1`) *** ## Citation count: total references to a source **Definition**: Total number of times a source is explicitly referenced in AI responses. ### Formula ``` Citation count = Total citations of the domain (or URL) ``` Each citation in a response counts separately. If one response cites two different URLs from the same domain, that's two citations for the domain. ``` Domain: techcrunch.com Time period: Last 7 days Response 1: Cited techcrunch.com/article-1, techcrunch.com/article-2 (2 citations) Response 2: Cited techcrunch.com/article-1 (1 citation) Response 3: Cited techcrunch.com/article-3 (1 citation) Response 4: No citations from techcrunch.com Citation count = 2 + 1 + 1 = 4 ``` Content from techcrunch.com was explicitly cited 4 times across 3 responses. *** ## Average citations per response **Definition**: The average number of times a source is cited per AI response where it appears. ### Formula ``` Average citations = Total citations of source / Responses where source was cited ``` Higher values indicate deeper trust -- when an AI model uses the source, it tends to cite multiple pages or cite it multiple times. ``` Domain: techcrunch.com Time period: Last 7 days Response 1: 2 citations from techcrunch.com Response 2: 1 citation from techcrunch.com Response 3: 1 citation from techcrunch.com Total: 4 citations across 3 responses Average citations = 4 / 3 = 1.33 ``` When AI uses techcrunch.com, it cites an average of 1.33 unique URLs from the domain per response. *** ## Used: how often a source appears **Definition**: The share of AI responses that cited this source within the selected time window. The same definition applies at both domain and URL level. ### Formula ``` Used (%) = (Responses that cited the source / Total responses analyzed) x 100 ``` Citation depth — how many URLs from the same domain show up when it's cited — is reported separately as **average citations per response** (defined above). ``` Domain: techcrunch.com Time period: Last 7 days Response 1: Cited 2 URLs from techcrunch.com Response 2: Cited 1 URL from techcrunch.com Response 3-100: No citations from techcrunch.com Responses citing techcrunch.com = 2 Total responses analyzed = 100 Used = (2 / 100) x 100 = 2% ``` techcrunch.com appeared as a source in 2% of AI responses for this project. Across those 2 responses, it averaged 1.5 unique URLs cited per response (reported separately as **avg citations**). ``` URL: techcrunch.com/article-1 Time period: Last 7 days Response 1: Cited this URL Response 2: Cited this URL Responses 3-100: Did not cite this URL Used = (2 / 100) x 100 = 2% ``` This specific article was cited in 2% of responses, regardless of how many times it was cited within each one. The "used" metric is your share-of-voice equivalent for sources: it tells you how often AI reaches for this source when answering questions in your space. *** ## Source categories Topify.ai classifies cited domains into categories to help you understand the source landscape: | Category | Examples | Description | | ----------------- | ----------------------------------- | --------------------------------------------- | | **Corporate** | apple.com, tesla.com | Official brand and company websites | | **Editorial** | nytimes.com, techcrunch.com | News outlets and journalism | | **Institutional** | .gov, .edu, who.int | Government, academic, and institutional sites | | **Reference** | wikipedia.org, stackoverflow\.com | Knowledge bases and reference platforms | | **UGC** | reddit.com, youtube.com, medium.com | User-generated content platforms | | **Other** | Edge cases, parked domains | Everything that doesn't fit the above | These categories help you understand whether AI providers are citing authoritative sources, user-generated content, or your competitors' sites when answering prompts related to your brand. *** ## Using sources data to improve your strategy Sources data helps you answer questions like: * **Which of your pages are already being cited?** Look for your domain in the top domains list. High citation counts mean AI providers trust your content. * **Where are the gaps?** If competitor domains are cited frequently but yours isn't, you need content that covers those topics. * **What content types perform best?** Compare citation rates across source categories to understand what AI providers prefer to cite. * **Which pages should you optimize?** URLs with high "used" counts are already influencing AI responses -- improving them could increase your visibility further. ## FAQ ### How do I get my own pages cited more often? Look at the domains AI currently cites most for your prompts and study what they have in common: structured headings that match the question, statistics and named numbers, citations to authoritative sources, and a confident tone. Then write content that does those things better. The AI agent's `geo_optimize` skill applies these techniques to existing pages automatically — see [Using the AI agent](/guides/using-ai-agent). ### My domain isn't in the source list — why? Either AI providers haven't picked up your pages yet, or they have but cite other sources first when answering your tracked prompts. Both are content problems, not data problems: you can fix them by publishing pages targeted at the prompts where your domain is missing, or by improving the pages you already have so they're more citable. # Terminology Source: https://docs.topify.ai/concepts/terminology Core terms and concepts used across the Topify.ai platform. ## Key terms ### Response An individual AI-generated answer to a prompt. Also referred to as an **LLM response** or **chat**. Each response is collected from an AI provider (ChatGPT, Perplexity, Google AI Overview, Gemini, or Claude) and stored for analysis. ### Prompt A question or query sent to AI platforms. Prompts represent the search queries your target audience might ask. **Example**: "Which MOBA games are best for beginners?" Topify.ai sends each prompt to multiple AI providers and collects their responses over time. ### Volume Estimated AI search demand for a prompt -- how frequently people are likely asking similar questions to AI tools. Derived from keyword research signals, topic coherence, and intent type. This is **not** a count of responses collected by Topify.ai. See [Volume](/concepts/brand-metrics#volume) for details. ### Visibility A brand is **visible** when it appears in the list of brands extracted from a response. Visibility is a prerequisite for sentiment and position — if a brand isn't mentioned, those metrics aren't computed for that response. ### Position rank Your brand's order among brands named in a response. For each response that mentions your brand, every brand is sorted by where it's first named (lowest character offset wins); your brand's rank in that response is its index + 1. * Rank `1` = you're the first brand named * Rank `3` = two brands are named before you in that response * The dashboard shows the **average rank** across responses where your brand appears Character offsets are computed internally but not surfaced in the UI. What you see is the rank. ## Metrics at a glance Topify.ai tracks five categories of metrics across your project. | Metric | What it measures | Scale | | -------------------------------------------------------------------------------------------------- | --------------------------------------------------- | --------------------------- | | [Visibility](/concepts/brand-metrics#visibility) | How often your brand appears in AI responses | 0--100% | | [Sentiment](/concepts/brand-metrics#sentiment) | How positively your brand is portrayed | 0--100 | | [Position](/concepts/brand-metrics#position) | Where your brand appears relative to competitors | Rank (1 = best) | | [Share of voice](/concepts/brand-metrics#share-of-voice-sov-your-slice-of-the-brand-mention-slots) | Your share of all brand-mention slots in AI answers | 0--100% | | [Volume](/concepts/brand-metrics#volume) | Estimated AI search demand for a prompt | Estimated monthly queries | | [Sources](/concepts/source-metrics) | How your content is cited by AI providers | Citation count, %, averages | ## What "good" looks like Each metric describes a different facet of your AI presence. None of them stands alone — they make sense together. * **Visibility** is your share of voice inside AI answers. High visibility means your brand is consistently named when buyers ask AI about your category. **It is not the same as impressions** — visibility tells you *whether* you appear, not *how many people see* the answer. * **Volume** is the demand side. It estimates how often real users actually ask each prompt. A prompt with high volume but low visibility is a high-leverage gap; a prompt with high visibility but near-zero volume is a vanity win. * **Visibility × Volume** is the closest proxy to real AI-driven impressions. Optimize for prompts that score well on both. * **Sentiment** only matters once visibility exists. A high sentiment with zero visibility means nothing — there were no mentions to score. * **Position** only matters once visibility exists, for the same reason. Once you do appear, an earlier position usually translates to higher buyer recall. * **Share of voice (SOV)** complements visibility by measuring how *crowded* the answers are. High visibility + low SOV means you appear often but in answers that name many brands; high visibility + high SOV means you dominate the conversation. * **Sources** explain *why* your visibility is what it is. The websites AI cites most often for your prompts are the model's "trusted answers" — earning a place there is one of the most direct ways to lift visibility. ## Filters All metrics can be filtered by **time period**, **AI provider**, and **topic tags**. For details on each filter and how they affect what's shown on the dashboard, see [Overview dashboard › Global filters](/guides/overview-dashboard#global-filters). # Analyzing sources Source: https://docs.topify.ai/guides/analyzing-sources Understand which domains and URLs AI models cite when answering prompts about your brand. The sources page shows which websites AI providers reference when generating responses to your prompts. This helps you understand where AI models get their information and where your content could earn more citations. ## Two views Aggregates citations at the domain level (e.g., all pages on `techcrunch.com`). Shows: | Column | Description | | ------------------- | ---------------------------------------------------------------- | | **Domain** | The website domain | | **Domain type** | Category (Corporate, Editorial, Reference, UGC, etc.) | | **Used %** | How much influence this domain has on AI responses | | **Avg citations** | Average number of citations per response when the domain appears | | **Mentioned** | Whether your brand was mentioned alongside this domain | | **Brands appeared** | Which brands appear in responses that cite this domain | Shows individual pages that were cited. Shows: | Column | Description | | ------------------- | --------------------------------------------------- | | **URL** | The specific page | | **Title** | Page title | | **Type** | Source category | | **Citation count** | Total times this URL was cited | | **Mentioned** | Whether your brand was mentioned alongside this URL | | **Brands appeared** | Brands in responses that cite this URL | ## Source detail page Click on any domain to see its detail page, which includes: * All individual URLs from that domain that were cited * Citation trends over time * The AI responses that referenced this domain ## Opportunity panel The sources page includes an opportunity panel that highlights content gaps -- areas where competitor domains are being cited but yours is not. These represent topics where creating or improving content could help your brand earn more AI citations. ## How to use sources data Sources data helps you answer key questions: * **Are your pages being cited?** Look for your domain in the list. High citation counts mean AI providers trust your content. * **Where are the gaps?** If competitor domains are cited frequently for topics you cover, your content may need improvement. * **What content types does AI prefer?** Compare citation rates across source categories to understand what AI providers tend to reference. * **Which pages should you optimize?** Pages already being cited are good candidates for further optimization to increase their influence. See [Source metrics](/concepts/source-metrics) for detailed explanations of citation count, average citations, and used percentage. ## FAQ ### A competitor's domain dominates the source list — what do I do? Treat it as a content blueprint, not a problem. Look at which of their pages are getting cited most, what those pages have in common (structured Q\&A headings, statistics, citations to authoritative sources), and write your own content that does those things at least as well. The agent's `content_gap` skill can run this comparison for you automatically — see [Using the AI agent](/guides/using-ai-agent). # Generating content Source: https://docs.topify.ai/guides/generating-content The Optimization > Actions form (prompt, keywords, references, file uploads, word count) and the research → draft → GEO-optimize pipeline that produces a finished article. This page covers the article-generation workflow under **Optimization > Actions**: the inputs you supply (prompt, optional keywords, reference links, files, word count) and the asynchronous pipeline that turns them into a draft with SEO metadata and images. ## Creating an article Navigate to **Optimization > Actions** and fill in the content configuration form. ### Required input * **Prompt** -- describe the article topic and any specific instructions (1--1,000 characters) ### Optional inputs * **Keywords** -- up to 10 target keywords the article should incorporate * **Reference links** -- up to 5 URLs that the article should draw information from * **File uploads** -- reference documents in PDF, DOCX, CSV, or XLSX format * **Word count** -- minimum and maximum word count targets (100--10,000) Providing keywords and reference links produces more targeted, higher-quality articles. The more context you give, the better the output. ## How generation works After you submit, Topify runs a multi-step pipeline in the background: **research → reference extraction → file processing → outline → drafting → GEO optimization → image generation → HTML conversion**. The pipeline runs asynchronously; you can check progress from the generation history table. For a step-by-step walkthrough including the research dossier, outline approval, and publish flow, see [Tutorial: Generate articles](/tutorials/generate-articles). ## Generation history The history table lists all your generated articles with: * **Title** -- article title * **Status** -- pending, processing, completed, or failed * **Data sources** -- what inputs were used (web search, reference links, uploaded files) * **Created date** Click on any article to view its full output. ## Article output A completed article includes: * **Content** -- full article in Markdown format * **SEO metadata** -- optimized title, meta description, and URL slug * **Keywords** -- target keywords used in the article * **Featured images** -- generated images (when available) ## Editing and publishing After generation, you can: * **Edit** the article content, title, slug, and SEO fields directly in the platform * **Change status** between draft, published, and archived * **Regenerate images** if you want different featured images ## Plan limits Each article consumes one generation credit. See [Billing and plans](/platform/billing-and-plans) for credit allowances and how unused credits accumulate. ## FAQ ### Does a failed generation use a credit? No. Credits are consumed only when an article completes successfully. If any step in the pipeline fails, you can retry the same task at no cost. ### What types of content can Topify generate? Long-form articles optimized for AI visibility using proven GEO techniques — blog posts, thought leadership articles, comparison pieces, and educational guides. Each one incorporates citations, statistics, and authoritative language designed to maximize the chance AI providers will quote it. ### How does AI-optimized content differ from traditional SEO content? Traditional SEO content optimizes for keyword density, backlinks, and meta tags so it ranks in Google's blue links. AI-optimized (GEO) content optimizes for being **quoted by an AI assistant**: clear factual statements, named statistics, source citations, and an authoritative tone that AI models are willing to surface in their answers. # Managing prompts Source: https://docs.topify.ai/guides/managing-prompts Add, organize, and manage the AI search prompts that Topify.ai tracks for your brand. This page covers day-to-day operations on the **Tracking > Prompts** screen: adding custom prompts, working through suggestions, and assigning topics. For background on what a prompt is and the active/suggested/inactive states, see [Understanding prompts](/guides/understanding-prompts). ## Adding prompts ### Custom 1. Click **Add prompt** on the active prompts tab. 2. Enter your prompt text (10--2,000 characters). This should be a natural question someone might ask an AI assistant. 3. Select a **location** (country) for localized responses. 4. Assign the prompt to a **topic** for organization. The number of active prompts you can have depends on your plan. See [Billing and plans](/platform/billing-and-plans) for limits. **Good prompts** are specific questions your target audience would ask: * "What are the best project management tools for remote teams?" * "Which CRM software is best for small businesses?" * "How do I choose a cloud hosting provider?" ### From research If your project has imported research output from prompt discovery, you can promote those prompts to active here. This saves time compared to manually adding them one by one. ## Prompt actions | Action | What it does | | ----------- | -------------------------------------------------- | | **Track** | Move a suggested prompt to active tracking | | **Reject** | Dismiss a suggested prompt | | **Untrack** | Stop tracking an active prompt (moves to inactive) | You can also perform **batch operations** to track or untrack multiple prompts at once. ## Prompt detail view Click on any active prompt to see its detailed analytics: * **Per-provider metrics** -- how your brand performs on each AI provider for this specific prompt * **Domains** -- which websites are cited in responses to this prompt * **URLs** -- specific pages cited as sources * **AI responses** -- the actual AI-generated responses collected From the domains view, you can drill down further into a specific domain to see all the URLs from that domain and the responses that cited them. ## Organizing with topics Topics group related prompts together. This helps you: * See aggregated metrics per topic on the overview dashboard * Filter and find prompts more easily * Understand which categories of questions perform best You can create, rename, and reorganize topics from the topics management page. For guidance on **how many prompts to track** and **how suggestions are generated**, see the [Understanding prompts FAQ](/guides/understanding-prompts#faq). # Overview dashboard Source: https://docs.topify.ai/guides/overview-dashboard Read the Visibility / Sentiment / Position tabs, the competitor ranking table, and the topic performance breakdown. The overview dashboard is the first thing you see after your project finishes bootstrapping. It gives you a high-level picture of how your brand performs across AI search engines. ## Metric tabs The dashboard has three main views, accessible via tabs at the top of the brand chart: | Tab | What it shows | | -------------- | ------------------------------------------------------------------- | | **Visibility** | How often your brand is mentioned in AI responses (as a percentage) | | **Sentiment** | How positively your brand is portrayed (0--100 scale) | | **Position** | Where your brand appears relative to competitors in responses | Each tab shows a time-series chart so you can track trends over your selected time period. See [Brand metrics](/concepts/brand-metrics) for detailed explanations of how each metric is calculated. ## Competitor ranking table Below the chart, a ranking table shows the top competitors for the selected metric. The table includes: * **Rank** -- position relative to other brands * **Brand name** -- competitor name and icon * **Metric score** -- visibility %, sentiment score, or position rank (depending on active tab) Your own brand is always shown in the table. If it falls outside the top 6, it appears at the bottom with its actual rank preserved. ## Topic performance breakdown The topic table groups your prompts by topic and shows aggregated metrics for each: * **Topic name** and prompt count * **AI volume** -- estimated AI search demand * **Visibility %** -- brand mention rate * **Sentiment** -- average sentiment score * **Position** -- average position rank You can export this table to CSV using the export button in the header. ## Global filters Filters at the top of the page control what data is displayed across all dashboard sections. ### Date range Select a time period for the data: * Last 7 days (default) * Last 14 days * Last 30 days * Last 90 days ### AI provider Filter by which AI search engine generated the responses — **All** (combined), or any single provider. See [Integrations](/platform/integrations) for the full provider list and which platforms count toward your plan. ### Topics If you've organized your prompts into topics, you can filter the dashboard to show metrics for specific topics only. ## Exporting data Click the **Export** button in the dashboard header to download your topic performance data as a CSV file. The export includes all visible columns and respects your current filter selections. ## FAQ ### How often is the dashboard data updated? Topify collects AI responses daily. Your dashboard metrics — visibility, sentiment, position, and volume — refresh each day based on the latest batch. Historical trend data is available in 7, 14, 30, and 90-day windows. ### What does it mean if my visibility score suddenly drops? A drop means fewer AI responses are mentioning your brand for your tracked prompts. Common causes: a competitor launching new content that displaces your mentions, an AI model update that changes response patterns, or seasonal shifts in how AI handles queries in your category. Check the per-prompt breakdown to identify which specific prompts lost visibility. ### Why does my brand sit below competitors I'm clearly bigger than? AI search competition isn't the same as market competition. Smaller competitors with more *citable* content — clearer headings, more statistics, more cited sources — often outrank larger brands in AI answers. The fix is content quality, not company size. Look at the Sources page to see what kinds of pages AI currently prefers for your prompts. # Tracking competitors Source: https://docs.topify.ai/guides/tracking-competitors Discover, track, and compare competitor brand visibility in AI search results. ## Why tracking competitors matters AI search competition isn't the same as market competition — Topify shows you the brands AI actually surfaces alongside (or instead of) yours, so you can compare your position over time and decide who to outrank. Topify.ai automatically discovers competitors by analyzing which brands appear alongside yours in AI responses. You can also add competitors manually. For background on how competitors are detected and the suggested/active/inactive lifecycle, see [Understanding competitors](/guides/understanding-competitors). ## The competitors page The competitors page shows a table with your actively tracked competitors, ranked by the selected metric: * **Rank** -- position relative to other tracked brands * **Brand name** -- competitor name and website * **Visibility %** -- how often they appear in AI responses * **Sentiment** -- how positively they are mentioned * **Position** -- how early they appear in responses You can sort the table by any metric column and use the search bar to find a specific competitor. ## Managing competitors ### Accepting suggested competitors When Topify.ai discovers new competitors, they appear in a **Suggested** section. For each suggestion, you can: * **Track** -- start actively monitoring this competitor * **Reject** -- dismiss the suggestion ### Adding competitors manually Click **Add competitor** and enter the brand name. Topify.ai will start tracking mentions of that brand in AI responses to your prompts. ### Removing a competitor You can remove an active competitor to stop tracking it. This moves it to the inactive state. Removing a competitor does not delete historical data. ## Competitor limits The number of active competitors you can track depends on your plan — see [Billing and plans](/platform/billing-and-plans) or the [pricing page](https://topify.ai/pricing) for current limits. ## FAQ ### Can I add competitors that AI hasn't mentioned yet? Yes. From **Preferences > Competitors > Add competitor**, type the brand name and Topify will start watching for it in every response we collect going forward. This is useful for emerging competitors or adjacent-category brands you want to monitor before they become AI-visible. ### Does removing a competitor delete its history? No. Removing a competitor moves it to the inactive state — it stops accumulating new metrics and no longer appears on the overview dashboard, but its historical data is preserved. You can reactivate it later if you change your mind. ### Do suggested competitors count toward my plan limit? No. Only **active** competitors count. Suggestions sit outside the limit until you confirm them. # Understanding competitors Source: https://docs.topify.ai/guides/understanding-competitors How a competitor is defined in AI search, how auto-detection vs manual addition work, and the suggested / active / inactive lifecycle. Competitors are brands that appear alongside yours in AI-generated responses. Tracking them helps you understand your market position in AI search and identify who you're competing against for visibility. ## What is a competitor? In the context of AI search, a competitor is any brand that AI providers mention in response to the same prompts as your brand. These may differ from your traditional business competitors -- they are the brands that AI models associate with the same topics and questions. For example, if you're a CRM company and you track the prompt "What are the best CRM tools?", any brand mentioned in the AI response (Salesforce, HubSpot, Zoho) is a competitor in this context. ## How competitors are discovered Topify.ai uses two methods to identify competitors: During bootstrapping and daily analysis, Topify.ai extracts brand names from every AI response. Brands that appear frequently across your tracked prompts are flagged as potential competitors. You can add competitors manually if you know specific brands you want to track that haven't been auto-detected yet. ## Competitor lifecycle Every competitor goes through a defined lifecycle: | State | Description | | ------------- | --------------------------------------------------------------------------------------------------- | | **Suggested** | Newly discovered by Topify.ai. Waiting for you to accept or reject. | | **Active** | You confirmed this competitor. Daily metrics are collected and it appears on your dashboard charts. | | **Inactive** | You rejected or removed this competitor. It no longer appears in tracking. | Only **active** competitors appear on your overview dashboard and count toward your plan's competitor limit. Historical data is preserved when you deactivate a competitor. ## What is tracked for each competitor Every active competitor gets the same four metrics as your own brand — **visibility**, **sentiment**, **position**, and **total mentions in the selected window** — collected daily across all monitored AI providers. See [Brand metrics](/concepts/brand-metrics) for definitions and formulas. Trends appear on the overview dashboard so you can track how your position moves relative to each competitor. ## Why competitor tracking matters Competitor data answers critical questions about your AI visibility strategy: * **Who are you competing against?** AI search may surface different competitors than traditional search. * **Are you gaining or losing ground?** Trend data shows whether your visibility is improving relative to competitors. * **Which competitors appear for which prompts?** Prompt-level competitor data reveals where competition is strongest. * **How does sentiment compare?** Understanding whether competitors are portrayed more positively helps you identify areas for content improvement. For instructions on managing your competitor list, see [Tracking competitors](/guides/tracking-competitors). ## FAQ ### Why is a known competitor not showing up in my list? Two common reasons: (1) the competitor wasn't mentioned in any responses to your tracked prompts during the time window — AI search competition is prompt-dependent, so a major business competitor may not appear if buyers ask about a different category in AI; (2) the competitor was mentioned but our brand-extraction missed an unusual spelling or alias. Add the brand manually from **Preferences > Competitors > Add competitor** to start tracking it explicitly. # Understanding prompts Source: https://docs.topify.ai/guides/understanding-prompts Learn what prompts are, how they drive your brand monitoring, and why they matter for AI visibility. Prompts are the foundation of Topify.ai's performance tracking. Each prompt represents a question that real users ask AI assistants -- and that Topify.ai sends to AI providers daily on your behalf. ## What is a prompt? A prompt is a natural-language query that your target audience might type into an AI search engine. For example: * "What is the best project management tool for startups?" * "Which CRM has the best automation features?" * "How do I choose a web hosting provider?" Topify.ai sends each active prompt to all monitored AI providers (see [Integrations](/platform/integrations) for the full list) and collects the responses. These responses are then analyzed to determine whether your brand was mentioned, how it was portrayed, and what other brands appeared. ## Why prompts matter Prompts are the lens through which you monitor your brand's AI visibility. Choosing the right prompts means you're tracking the conversations that matter most to your business. Each prompt generates a set of metrics: | Metric | What it tells you | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Visibility** | How often your brand appears in responses to this prompt (as a percentage across providers) | | **Sentiment** | How positively your brand is described when it appears | | **Position** | Where your brand appears in the response relative to competitors | | **CVR** | Citation Visibility Rate -- a composite score (0--100) blending visibility, sentiment, and position into a single quick-glance prominence score for this prompt | | **Volume** | How many AI responses have been collected and analyzed | ## Types of prompts Prompts that Topify.ai tracks daily. Responses are collected and analyzed each day, and metrics appear on your dashboard. AI-generated prompt recommendations based on your brand and industry. These are not tracked until you activate them. Previously active prompts that you stopped tracking. Historical data is preserved. ## How AI search volume is estimated Volume estimates how often a question is being asked across AI platforms. Topify extracts keywords from each prompt, queries Google Ads search volume data as a baseline, then applies AI-specific adjustments — including intent classification and topic coherence — to estimate AI-specific query volume. ## Prompt intent Every prompt carries an intent that influences how metrics are weighted: | Intent | Description | Example | | ----------------- | ------------------------------------------------- | ----------------------------------- | | **Commercial** | Evaluating products or services before a purchase | "Best CRM for small businesses" | | **Transactional** | Ready to take action or buy | "Sign up for Salesforce free trial" | | **Informational** | Seeking knowledge or understanding | "What is a CRM?" | | **Navigational** | Looking for a specific brand or page | "HubSpot pricing page" | Commercial and transactional prompts typically carry higher business value because they represent users closer to a purchasing decision. For a walkthrough on adding and organizing prompts, see [Managing prompts](/guides/managing-prompts). ## FAQ ### How should I structure my prompt set? Cover the four intent types — informational, commercial, transactional, navigational — so you see the full buyer journey, not just one slice. Then layer in **supporting prompts** that catch the long-tail variations of each core question. For example, alongside "best CRM" track "best CRM for 5-person sales team", "best CRM under \$50/seat", and "best CRM for B2B SaaS startups." Real buyers ask the long-tail versions far more than the generic ones, and AI answers can vary a lot between them. ### Can I track prompts in multiple languages? Yes. Each project has a default language, but you can add prompts in any language from **Tracking > Prompts > + Add Prompts**. AI providers respond in the prompt's language and metrics are calculated the same way. If you operate in several markets, consider one project per language so the dashboards stay clean. ### How many prompts should I track? Start with **15–30 active prompts** that cover your core products, top use cases, and common buyer questions. Most projects don't need more than that to get a clear picture. Refine over time — drop the noisy ones, add new ones as themes emerge from your data. # Understanding sources Source: https://docs.topify.ai/guides/understanding-sources What counts as a source citation, the difference between domain- and URL-level tracking, and how source categories are assigned. A source is any webpage an AI provider attaches to its response as a reference. Topify records every cited URL across your tracked prompts and rolls them up at both the domain and URL level, so you can see which sites AI reaches for and which specific pages it lands on. ## What is a source? When AI assistants answer a question, they often cite the websites they drew information from. These cited websites are **sources**. For example, when answering "What is the best CRM?", Perplexity might cite: * `g2.com/categories/crm` (a review platform) * `hubspot.com/products/crm` (a vendor page) * `forbes.com/advisor/business/best-crm` (an editorial article) Each of these is a source. Topify.ai collects and analyzes all sources cited across your tracked prompts. ## Why sources matter Source citations are a direct measure of content authority in AI search. If your website is frequently cited as a source: * AI providers consider your content **trustworthy and informative** * Your brand gets **direct exposure** through link attribution * Users following citations land **on your website** Conversely, if competitor domains are cited frequently but yours isn't, it signals a content gap that you can address. ## Domain vs URL tracking Topify.ai tracks sources at two levels: Aggregated view across all pages on a domain. Useful for understanding which websites AI providers trust most overall. **Example**: All citations to `techcrunch.com/*` are grouped together. Individual page tracking. Useful for identifying which specific content pieces are being cited. **Example**: `techcrunch.com/2026/01/best-crm-tools` is tracked as a separate entry. ## Source categories and metrics Each cited domain is classified into a category (Corporate, Editorial, Institutional, Reference, UGC, Other) and tracked with metrics like **Used %**, **avg citations**, and **citation count**. Understanding which categories dominate helps you decide where to focus content efforts — if editorial outlets dominate your space, earning media coverage there is a leverage point. For full category definitions and metric formulas, see [Source metrics](/concepts/source-metrics). For how to use the sources page in the dashboard, see [Analyzing sources](/guides/analyzing-sources). ## FAQ ### Why do different AI providers cite different sources? Each provider has its own training data, retrieval system, and citation preferences, so the same question can produce quite different source lists across them. Topify tracks source citations per provider so you can see where each AI engine draws from and optimize accordingly. ### How can I get my website cited more often by AI? Focus on creating authoritative, well-structured content that AI providers can easily reference. Key strategies: add citations and statistics to your content, use clear headings that match common questions, keep technical documentation up to date, and build topical authority through comprehensive coverage of your domain. # Using the AI agent Source: https://docs.topify.ai/guides/using-ai-agent An AI-powered agent that performs keyword research, site analysis, content creation, and GEO optimization to improve your brand's AI search visibility. ## What the agent is and when to use it Topify.ai's AI agent is a chat-based interface to Topify's research, content, and optimization tools. Instead of clicking through dashboards, you describe what you need in natural language and the agent picks the right **skill** -- a specialized capability like keyword research, site profiling, or article writing -- and runs it for you. Use the agent whenever you want to combine multiple capabilities (research → strategy → content) in one conversation, or whenever a task crosses several data sources at once. ## How to interact with the agent The agent understands natural language. Describe your goal, and it determines which skill to use and what information it needs. **Example interactions:** * "Research keywords for my website example.com" * "Analyze the SEO health of example.com" * "Write an article about cloud hosting best practices" * "Optimize this content for AI search visibility" (with content provided) * "Find content gaps between my site and these two competitors" If the agent needs more information to complete a task, it will ask you follow-up questions before proceeding. ### Multi-step workflows For complex requests, the agent breaks the work into subtasks and executes them in sequence. For example, asking "Research keywords and create a content strategy for my website" triggers: 1. Site profiling to understand your brand 2. Keyword research using the profile 3. Content strategy built on the keyword findings Each step builds on the output of the previous one, and all results are available for reference in follow-up conversations. ## Agent capabilities The agent's skills span four areas: research, content, site analysis, and GEO optimization. Each skill is backed by specialized data sources and can be combined into multi-step workflows. ### Research Discover high-value keywords by analyzing your site, competitors, and search data. **What it does:** * Generates seed keywords from your site profile and brand context * Expands seeds using search volume, difficulty, and competition data from Semrush * Supplements with competitor keyword data to find opportunities they rank for * Deduplicates and clusters keywords by topic * Scores each keyword for business relevance to your brand **Output:** A structured keyword list with search volume, keyword difficulty, search intent, and business relevance scores. **Example:** "Research keywords for my e-commerce store that sells running shoes" Identify your top competitors and analyze their keyword strategies. **What it does:** * Discovers competitors based on keyword overlap and market positioning * Analyzes competitor keyword portfolios and rankings * Compares content strategies across competitors * Provides strategic recommendations for differentiation **Output:** Competitor profiles with keyword overlap analysis, content strengths, and actionable recommendations. **Example:** "Who are my top competitors and what keywords are they ranking for?" ### Content Generate a strategic content plan tailored to your brand and audience. **What it does:** * Analyzes your existing content landscape * Maps keywords to content topics and formats * Prioritizes topics by business impact and competition level * Creates a content calendar with publishing cadence recommendations **Output:** A prioritized content plan with topic clusters, suggested formats, and a publishing timeline. **Example:** "Create a 3-month content strategy for my SaaS blog" Create a full SEO-optimized article with structured content. **What it does:** * Researches the topic using web search and your provided references * Generates a detailed outline with section structure * Writes the full article with keyword integration * Applies GEO optimization techniques for AI search visibility * Generates SEO metadata (title, description, slug) **Output:** A complete article in Markdown with SEO metadata and keyword targeting. **Example:** "Write an article about the best practices for cloud migration" Write and publish an article directly to your CMS. **What it does:** * Writes the article (same process as above) * Formats content for your CMS platform * Uploads featured images * Applies categories, tags, and SEO metadata * Publishes or saves as draft based on your preference **Supported platforms:** WordPress, Shopify, Framer **Example:** "Write and publish an article about email marketing trends to my WordPress blog" ### Site analysis Analyze what a website is about and how it positions itself. **What it does:** * Crawls your website to understand its structure and content * Identifies your business model, products, and services * Maps your target audience and content themes * Assesses brand positioning and messaging **Output:** A comprehensive site profile covering business model, industry, offerings, audience, and content themes. **Example:** "Analyze my website example.com and tell me what you find" Run a comprehensive audit covering multiple dimensions of search performance. **What it does:** * Audits technical SEO health (if site access is available) * Pulls search performance data from Google Search Console * Checks keyword rankings and SERP data * Benchmarks against competitors **Data sources:** Google Search Console, Semrush **Output:** A multi-dimensional SEO audit with scores, identified issues, and prioritized recommendations. **Example:** "Run a full SEO analysis on my website" SEO analysis is most effective when you've connected your Google Search Console account. See [Integrations](/platform/integrations) for setup instructions. Analyze your Google Search Console data for trends and insights. **What it does:** * Pulls click, impression, CTR, and position data from GSC * Identifies top-performing queries and pages * Surfaces declining or rising trends * Highlights opportunities for quick wins **Output:** Performance summary with trend analysis and actionable insights. **Example:** "Show me my search performance trends for the last 3 months" ### GEO optimization Compare your content against competitors to find what you're missing. **What it does:** * Crawls and analyzes content from 2--5 competitor sites * Compares topic coverage, content depth, and formats * Identifies AI citation gaps using GEO methodology * Categorizes opportunities by effort and impact: * **Quick wins** -- low effort, high impact topics you can address immediately * **Strategic builds** -- medium effort topics that strengthen your content foundation * **Long-term investments** -- high effort topics that establish authority over time **Output:** A gap analysis report with prioritized content opportunities and competitive comparisons. **Example:** "Find content gaps between my site and competitor1.com and competitor2.com" Rewrites existing content to maximize AI search visibility, applying the eight Princeton GEO techniques (citations, statistics, quotations, authoritative tone, etc.) most relevant to your industry. **Output:** Optimized content with tracked changes and an explanation of each applied technique. **Example:** "Optimize this blog post for AI search visibility" See [GEO methodology](/concepts/geo-methodology) for the full technique table, measured visibility lifts, and per-industry recommendations. ## Approval workflows For certain tasks, the agent pauses to get your confirmation before proceeding. For example: * **Competitor confirmation** -- after discovering competitors during site profiling, the agent presents the list for your review before saving * **Publishing** -- before publishing to your CMS, the agent shows the article for your approval ## Memory across sessions The agent remembers findings from previous interactions within your project. For example: * Site profiles are reused when writing articles so the agent understands your brand context * Keyword research results are available for content strategy and article writing * Competitor data is retained for gap analysis This means follow-up tasks build on previous work instead of starting from scratch. ## Data sources and integrations The agent connects to external services to power its analysis: | Data source | Used for | | ------------------------- | ------------------------------------------------------- | | **Semrush** | Keyword volume, difficulty, and competitor keyword data | | **Google Search Console** | Search clicks, impressions, CTR, and position data | | **Web scraper** | Crawling and extracting content from websites | Google Search Console requires OAuth2 authentication. Set it up in [Integrations](/platform/integrations) to get the most out of the agent's analysis capabilities. ## CMS publishing The agent can publish articles directly to your website through supported CMS platforms: Connects via the WordPress REST API. Supports: * Post creation with categories and tags * Featured image uploads * SEO metadata (compatible with Yoast and RankMath) * Draft or publish status **Setup**: provide your WordPress site URL, username, and an application password (not your login password). Connects via the Shopify GraphQL Admin API. Supports: * Blog article creation * Tags and status management * Blog selection (if you have multiple blogs) **Setup**: provide your store URL, admin API access token, and blog ID. Connects via the Framer CMS API. Supports: * Item creation in CMS collections * Automatic field mapping (title, body, excerpt, date, author) **Setup**: provide your project URL, API key, and target collection ID. ## FAQ ### Will the agent publish to my CMS without asking me? No. Publishing is gated behind an explicit approval step — the agent shows you the article, the destination CMS, and any tags/categories before pushing anything live. You can approve, edit, or reject. The same applies to other potentially-destructive actions (saving newly discovered competitors, applying GEO rewrites to existing pages). ### What if I haven't connected Google Search Console yet? Most agent skills work without GSC — keyword research, content strategy, content generation, and GEO optimization all run on Topify's own data and external research providers. The skills that benefit from GSC will tell you upfront and either ask you to connect it (instructions in [Integrations](/platform/integrations)) or fall back to a less-detailed analysis. # Introduction Source: https://docs.topify.ai/index Topify.ai is a Generative Engine Optimization (GEO) platform that helps brands track, analyze, and improve their visibility in AI-powered search engines. ## What is Topify.ai? Topify.ai tells you whether AI assistants — ChatGPT, Perplexity, Google AI Overview, Gemini, and Claude — are mentioning your brand when buyers ask questions in your category. Every day, it tracks across the responses those assistants generate: * **Visibility** — how often your brand is named * **Sentiment** — how positively you're described when you are * **Position** — how early you appear relative to competitors * **Share of voice** — your slice of the conversation vs every other brand named in your category * **Competitors** — which brands AI surfaces alongside (or instead of) you * **Sources** — which websites the AI cites as evidence You set up one **project** per brand you want to track. Each project has its own prompts, competitors, sources, and metrics. ## Why GEO matters This is **Generative Engine Optimization (GEO)** — the AI-era counterpart to SEO. Where SEO competes for clicks on a search results page, GEO competes for being **named in the answer itself**. Most GEO tools stop at data and advice. Topify also ships the **actionable items** that close the gap — AI-quotable articles, page rewrites, forum engagements — each tied back to the metric it's meant to move. ## Tools and capabilities Topify.ai is an ecosystem of tools that work together to help you understand and improve your AI visibility. Track brand visibility, sentiment, and position across AI providers. Monitor competitors, analyze citation sources, and identify content opportunities. Generate SEO and GEO-optimized articles from keywords, reference materials, and custom prompts. Articles are structured for AI citation and discoverability. An AI-powered GEO agent that performs site audits, keyword research, competitor analysis, and content optimization using techniques from Princeton's GEO research. The execution layer. Ships the actual fixes — AI-quotable articles, page rewrites, forum engagements — that move your metrics, not just recommendations to make them. ## AI platforms we monitor | Provider | Description | | ---------------------- | ------------------------------------------------------------------ | | **ChatGPT** (OpenAI) | The most widely used AI assistant, with web search capabilities | | **Perplexity** | An AI-native search engine that cites sources in every response | | **Google AI Overview** | Google's AI-generated summaries shown at the top of search results | | **Gemini** | Google's standalone conversational AI assistant | | **Claude** | Anthropic's conversational AI, accessed via the official API | Metrics are tracked per provider so you can see where you perform best and where to focus. ## Start tracking your brand Set up your first project and start tracking your brand in AI search results. # Billing and plans Source: https://docs.topify.ai/platform/billing-and-plans Subscription tiers, usage limits, and billing for Topify.ai. ## Plan comparison For the current pricing and full feature comparison across all plan tiers, see the [Topify.ai pricing page](https://topify.ai/pricing). Enterprise plans support custom limits. Contact us at [info@topify.ai](mailto:info@topify.ai) for a tailored plan. ## What each limit means | Limit | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Prompts | The total number of active prompts you can track across all projects. Each prompt is sent to up to five AI providers daily. | | Team seats | The number of people who can be part of your workspace. See [Team management](/platform/team-management) for details on inviting members. | | Research credits | Credits for using the research feature to explore topics and keywords. Each research query consumes one credit. Unused credits **accumulate month over month** — they do not reset, so anything you don't use carries forward into future billing periods. | | Content generation credits | Credits for generating articles. Each article consumes one credit. Unused credits **accumulate month over month** — they do not reset, so what you pay for is always available to use later. | | Active competitors | The number of competitor brands you can actively track at the same time. You can remove competitors to make room for new ones. | ## Tracking your usage You can check your current usage at any time from the subscription page. It shows: * Prompts used vs. limit * Research credits consumed this month * Content generation credits consumed this month * Team seats occupied vs. limit This is the fastest way to see how close you are to any of your plan's limits. ## Changing plans You can change your plan at any time from the subscription management page. * **Upgrading**: new limits take effect immediately, with prorated charging for the remainder of the current period. * **Downgrading**: the change takes effect at the end of your current billing period, so you keep the higher limits until then. ### Starting with a free trial You can try the Basic or Pro plan free for 30 days. During the trial, you have the same limits as the paid plan. At the end of the trial, your workspace loses paid access unless you subscribe. ## Billing cycles and payment Paid plans are available in **monthly** or **yearly** billing cycles. Yearly billing offers a discounted rate. We accept major credit cards (Visa, Mastercard, American Express). ## FAQ ### Can I switch plans mid-cycle? Yes. Upgrades take effect immediately, with prorated charging for the remainder of the current period. Downgrades take effect at the start of your next billing cycle, so you keep the higher limits until then. ### Do unused credits roll over? Yes — research credits and content generation credits accumulate. Anything you don't use this month carries forward into next month, so you never lose what you paid for. # Integrations Source: https://docs.topify.ai/platform/integrations The five tracked AI providers, the analytics connector used by the AI agent (Google Search Console + Semrush), and supported CMS publishing destinations. The integrations on this page fall into three groups: the AI search providers Topify queries to collect responses, the analytics platforms the AI agent connects to during analysis, and the CMS destinations the agent can publish articles to. ## AI search providers Topify.ai tracks your brand across five major AI search providers: | Provider | What it provides | | ---------------------- | -------------------------------------------------------------------------------------------------------------------- | | **ChatGPT** (OpenAI) | The most widely used AI assistant. Responses often mention well-known brands and provide detailed comparisons. | | **Perplexity** | An AI-native search engine that cites sources in every response. Particularly useful for tracking citation patterns. | | **Google AI Overview** | Google's AI-generated summaries shown at the top of search results. Reflects how Google's AI interprets your brand. | | **Gemini** | Google's standalone conversational AI assistant, with its own response patterns distinct from AI Overview. | | **Claude** | Anthropic's conversational AI, accessed via the official API. | Each provider has different behaviors -- some cite sources more often, some favor certain types of content. Topify.ai lets you filter metrics by provider to understand where your brand performs best. ## Analytics integrations (AI agent) The AI agent can connect to your analytics platforms to enrich its analysis: ### Google Search Console Connect GSC via OAuth to provide the agent with: * Search query performance (clicks, impressions, position) * Page-level search analytics * Keyword ranking data Analytics integrations require credentials to be configured in the AI agent. These connections are used by the agent during analysis -- they do not send data back to Topify.ai's servers. ## CMS publishing (AI agent) The AI agent can publish articles directly to your website. See [Using the AI agent](/guides/using-ai-agent) for setup details. | Platform | Connection method | Key features | | ------------- | ----------------------------------- | -------------------------------------------------------------- | | **WordPress** | REST API with application password | Categories, tags, featured images, Yoast/RankMath SEO metadata | | **Shopify** | GraphQL Admin API with access token | Blog articles, tags, draft/publish status | | **Framer** | CMS API with API key | Collection items, automatic field mapping | ## Data providers Behind the scenes, Topify.ai uses these services to power its analysis: | Service | Purpose | | ----------- | ---------------------------------------------------------------------------- | | **Semrush** | Keyword research data used during project initialization and by the AI agent | # Team management Source: https://docs.topify.ai/platform/team-management Create workspaces, invite team members, and manage access to your Topify.ai projects. ## Workspaces Every Topify.ai account belongs to at least one workspace (team). A workspace is created automatically when you first sign in. All projects, prompts, competitors, and generated content belong to a workspace. A user can belong to **multiple workspaces** — for example, an agency operator working with several clients, or a contractor on more than one team. Switch between them from the workspace selector in the sidebar. ## Inviting team members To add someone to your workspace: Navigate to **Management > Members** in the platform. Type the email address of the person you want to invite. If the person already has a Topify.ai account, they are added instantly. If not, they receive an invitation email with a verification code to create their account and join your workspace. Invitation codes expire after 10 minutes. If the invite expires, you can re-send it from the members page. ## Removing members **Admins** can remove any member from the workspace. **Viewers** can only remove themselves. The last remaining admin cannot be removed. To remove a member, go to **Management > Members**, find the person, and click remove. ## Seat limits The number of members your workspace can have depends on your plan. For current per-plan seat allowances, see the [pricing page](https://topify.ai/pricing). If your workspace is at its seat limit, you need to either upgrade your plan or remove an existing member before adding a new one. ## Permissions Topify has two roles: **admin** and **viewer**. | Capability | Admin | Viewer | | ---------------------------------- | ----- | ------------------------------- | | View dashboards and reports | Yes | Yes (only on assigned projects) | | Edit prompts, competitors, sources | Yes | No | | Generate content | Yes | No | | Use the AI agent | Yes | No | | Invite or remove team members | Yes | No | | Manage subscription and billing | Yes | No | **Viewers** are read-only and can only see the projects you explicitly grant them access to — useful for clients, executives, or contractors who need visibility into specific projects without the ability to make changes. **Admins** have full access across the workspace. The workspace creator is the billing owner and manages the subscription by default; other admins can be added as needed. ## FAQ ### Invite email never arrived — what do I do? Ask the invitee to check spam first; most missing invites end up there. Then verify the email address on the members page is right, and re-send (invite codes expire after 10 minutes). If they already have a Topify account at that email, they're added automatically and just need to sign in. ### Can one person be in multiple workspaces? Yes. The same email can be a member of multiple workspaces simultaneously. Switch between them from the workspace selector in the sidebar. ### What can admins and viewers each do? Admins have full access — manage projects, prompts, competitors, billing, and use the AI agent. Viewers are read-only, can only see the projects you explicitly grant access to, and **cannot use the agent or generate content**. See the Permissions table above for the full breakdown. # Quickstart Source: https://docs.topify.ai/quickstart Sign up, add your brand, review prompts, and read your first dashboard — end to end. Set up your first Topify.ai project end-to-end. By the end you'll have a tracked brand, a curated set of prompts, and a dashboard showing how often AI assistants mention your brand when buyers ask about your category. **Time to complete**: 2–5 minutes. ## Before you start You'll need: * A working email address * Your brand's website URL (the page a new customer would land on) * A payment method, or to start a free trial ## Steps Go to [app.topify.ai](https://app.topify.ai) and create an account using your work email. Verify your email if prompted, then sign in. A workspace is the top-level container for your team's projects — pick a name that matches your organization. If you track multiple brands or run an agency with multiple clients, each one will live as its own project inside this workspace. Pick a subscription plan, or start a free trial of Basic or Pro to evaluate the product first. The plan decides how many prompts you can track, how many AI providers we query, and how many articles you can generate per month. You can change plans later from **Settings > Billing**. See the [pricing page](https://topify.ai/pricing) for the full feature matrix. The onboarding wizard opens at the **Info** step. This is where you tell us what brand to track. Required: * **Brand name** — the exact name AI assistants would use when referring to you. This is what we look for in every AI response. * **Website URL** — your primary domain. We crawl this to understand what you sell, so we can suggest the right questions to track. Optional: * **Language** — defaults to English. Change this if your buyers ask AI in another language. * **Location** — defaults to United States. Change this if you sell into a specific region; AI answers can vary by location. If your brand has nicknames (e.g. "LoL" for League of Legends), don't worry about them yet — you can add **brand aliases** in project settings after setup so they count as mentions too. Click **Next** to advance to the Prompts step. A **prompt** is one of the actual questions a buyer in your category might ask an AI assistant. The set of prompts you track is the set of conversations Topify monitors for you, every day. Tracking the right prompts is the most important decision in setup — track the questions your buyers actually ask, and your dashboard will reflect real purchase intent. Topify analyzes your site and drafts a starter set of prompts grouped by topic (e.g. *"Best CRM for small teams"*, *"How to choose a CRM"*). Toggle on the prompts you want to track, expand topic groups to see more, or click **Add your own prompts** to type one in. Onboarding wizard on the Prompts step listing suggested prompts grouped by topic with checkboxes. You can add, edit, or remove prompts any time later from **Tracking > Prompts**. See [Tutorial: Manage prompts](/tutorials/manage-prompts). If the list comes back with very few prompts, the most common cause is that your website blocks bots (which means AI assistants can't read it either — fixing this often improves your real-world AI visibility too). See [Troubleshooting](/support/troubleshooting#topify-could-not-generate-prompts-for-my-brand). Click **Next** to start analysis. The wizard advances to the **Analyze** step and shows a progress bar. This usually takes 1–3 minutes. Onboarding wizard on the Analyze step with a progress bar at 29% and a 'Scanning your website...' status message. Behind the scenes Topify is doing three things in parallel — and each one feeds something useful onto your dashboard: 1. **Site fetch and topic understanding** — we crawl your site to understand your industry, products, and audience. This is what makes the prompt suggestions relevant in the first place. 2. **AI response collection** — we send your prompts to ChatGPT, Perplexity, and Google AI Overview, then store every response. This is the raw material for every metric you'll see. 3. **Response analysis** — we scan those responses to count mentions of your brand, detect competitors that show up alongside you, and note which websites the AI cited. This is what turns raw text into the dashboard's visibility, sentiment, position, and source numbers. By the time the bar fills, your dashboard already has a baseline competitor list, sentiment scores, and a citation graph — no extra setup needed. Analysis runs in the background. You can leave the page; the Analyze step refreshes from the server when you return, and you'll get an in-app notification when it completes. Once analysis finishes, the wizard reaches the **Done** step and you land on the overview dashboard with your first batch of metrics. Overview dashboard with brand performance line chart, Visibility / Sentiment / Position / SOV tabs, and a ranked competitors panel. Three things to look at first, and what each one is telling you: * **Visibility chart** — the share of AI answers that named your brand. This is your headline number. If it's lower than you expected, that's the gap to close. * **Competitor ranking** — where your brand sits relative to the competitors that came up in the same answers. Pay attention to the ones above you — those are the brands AI is recommending instead of you. * **Topic performance** — which clusters of prompts you do best (and worst) on. Your weakest topic is usually your biggest opportunity, because a single piece of strong content can lift a whole topic at once. See [Overview dashboard](/guides/overview-dashboard) for a deeper walkthrough of each section. Day-one numbers swing a lot — only a small batch of responses has been collected, and LLM outputs themselves vary run-to-run. Numbers tend to settle after the first few days as more responses average out the randomness. ## What's next Now that you have a baseline, the natural next moves are: tighten your prompt list to match what your buyers really ask, then start producing content the AI can quote. Add, organize, and tune the prompts Topify tracks for your brand. Use the content pipeline to create AI-optimized articles that earn mentions. Wire up Search Console and publishing platforms. Let the agent recommend and execute optimization actions for you. ## FAQ ### My dashboard looks empty — what now? Usually it's a filter issue, not missing data. Widen the date range to **30 days**, clear all provider and topic filters, and confirm the project status shows complete (not still `processing`). If it's still empty, check [Troubleshooting](/support/troubleshooting#dashboard-shows-no-data-after-setup-finished). ### Can I track multiple brands? Yes. Each brand lives as its own project inside your workspace, with its own prompts, competitors, and metrics. Add a new project from the workspace dashboard. ### My visibility number is low — how do I improve it? Three things tend to move the number, in order of impact: (1) make sure your prompts actually match the questions buyers in your category ask — see [Tutorial: Manage prompts](/tutorials/manage-prompts); (2) publish content the AI can quote, using [content generation](/tutorials/generate-articles); (3) optimize existing pages with the agent's `geo_optimize` skill — see [Using the AI agent](/guides/using-ai-agent). ### How long does the first analysis take? Usually 1–5 minutes, occasionally longer for very large sites. The wizard shows live progress and you can leave the page — analysis continues in the background and you'll get an in-app notification when it finishes. ### What if my website is behind a login or paywall? We can only analyze publicly-accessible pages. If your homepage and product/service pages are behind auth, we won't be able to read enough to generate good prompt suggestions. The fix is usually to expose at least one public landing page per major offering, then add custom prompts manually from **Tracking > Prompts > + Add Prompts**. ### Will my brand show up in AI answers right after bootstrap? Most established brands already appear in some AI responses, and Topify shows you which prompts you're winning on from day one. Newer or more niche brands may need to publish or improve content before AI assistants start citing them — start with [content generation](/tutorials/generate-articles). ### How long until I see results from optimizing? Topify's own tracking updates daily, so you'll see whether your visibility number is moving within a week. The AI providers themselves re-crawl on their own schedules — changes to your site usually show up in their answers within 2–4 weeks. ### Can I change my brand name or website later? Yes — both are editable from project settings. You can also add **brand aliases** (alternative names or spellings) so that mentions like "LoL" still count for "League of Legends." ### What if I'm in multiple workspaces? You can belong to multiple workspaces at the same time, but **agent and action features are only available on projects in workspaces where you're an admin**. Viewers can read dashboards on the projects they're granted access to, but can't run the AI agent or trigger actions (article generation, page rewrites, forum engagements, etc.). If you need agent/action access on a workspace where you're a viewer, ask that workspace's admin to upgrade your role. # Troubleshooting Source: https://docs.topify.ai/support/troubleshooting Common issues with Topify.ai — what went wrong, why it matters for your tracking, and how to fix it. Common Topify.ai issues, what each symptom means for your data, and how to fix it. If you don't see your issue here, email [info@topify.ai](mailto:info@topify.ai). Each Guides page also has its own FAQ section for topic-specific questions. When you contact support, including a screenshot of the error and your **project name** helps us debug much faster. ## Setting up a new project ### My new project is stuck on "analyzing" The first analysis after you create a project is the biggest one we run — we're crawling your website, generating prompts, and asking multiple AI providers about your category. It can take several minutes, especially for larger sites. **What to try, in order:** 1. **Wait 3–5 minutes.** This is normal for most new projects. 2. **Refresh the page.** The progress bar reads from the server when the page loads, so a stale page may not reflect current progress. 3. **Click Retry on the project page.** If the status shows `error`, this resolves most transient failures (a slow AI provider, a one-off timeout). 4. **Open your website in an incognito browser window.** If your site doesn't load there, our analyzer can't reach it either. 5. **Email support** if it's been stuck for more than 15 minutes — [info@topify.ai](mailto:info@topify.ai), include your project name. ### Topify could not generate prompts for my brand Why this matters: prompts are the questions Topify asks AI assistants on your behalf. **No prompts = nothing to track.** If you ended up with very few or zero suggestions, here's what's likely going on. The usual cause is that **we couldn't read enough from your website to understand what you actually do**. The most common reasons: * **Your site blocks bots.** Many sites block crawlers in `robots.txt`, by user-agent, or via Cloudflare/WAF rules. If we can't read your site, we have nothing to base prompts on. (Same problem affects whether AI assistants can read you in the first place — fixing this often improves your AI visibility too.) * **Your homepage renders entirely with JavaScript.** If the page is blank until JS runs, a basic crawl returns nothing useful. * **Your site is behind a login or paywall.** Anything that requires authentication is invisible to us. * **Your brand name is too generic.** If the brand name matches a common word, we can't tell what it refers to. * **The website URL is wrong or redirects elsewhere.** Double-check the URL in project settings. **What to do:** * Allow our crawler in `robots.txt` and any WAF/Cloudflare rules. We identify as a standard web client. * For JS-heavy sites, make sure the key pages have meaningful server-rendered HTML (titles, descriptions, product copy). * Add a few **custom prompts** manually from **Tracking > Prompts > + Add Prompts**. Once we have a few real examples of what you care about, suggested prompts work much better. * Edit your project's **brand description** in settings to give us more context about what you do. ### Setup finished but no AI responses were collected yet Different from the situation above — here, prompts exist but the per-prompt response counts are 0. That means we have a tracking plan but haven't actually heard back from the AI providers yet. **Likely causes and fixes:** * **All providers timed out on the first run.** AI providers occasionally rate-limit or go down. The next daily run will retry automatically, or click **Retry** on a prompt to re-fetch. * **The prompt got filtered.** Some providers refuse certain queries (medical advice, legal advice, adult content). Rewrite the prompt to be more neutral. * **You haven't activated any prompts yet.** Suggested prompts aren't tracked. Switch to the **Active** tab and confirm at least one prompt is there. ## Content generation ### Article generation failed What it means: a step in the writing pipeline (research → outline → draft → SEO) didn't complete, so no article was produced. The good news is **failed generations don't use a credit** — you can retry without cost. **Step-by-step diagnosis:** | Symptom | Likely cause | Fix | | -------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------ | | Failed instantly | Invalid input (empty prompt, bad URL) | Check prompt is 1–1,000 characters; verify reference URLs open in a browser | | Failed during **Research** | Web search provider down, or topic too niche | Wait a few minutes and retry. Add more keywords to give the researcher better signal | | Failed during **Reference extraction** | A reference URL is unreachable or blocks bots | Remove the failing URL or replace it with a working one | | Failed during **File processing** | Uploaded file is corrupted or unsupported | Re-export the file as PDF, DOCX, CSV, XLSX, or TXT and re-upload | | Failed during **Drafting** | AI model temporarily overloaded | Retry — most drafting failures are transient | | Failed during **SEO optimization** | Same as drafting | Retry | To retry, open the failed task and click **Regenerate**, or create a new task with the same inputs. ### Article was generated but quality is poor Quality issues almost always come from thin input rather than the model itself. The richer the brief, the better the article. **Things to add:** * **A specific angle.** "Write about CRMs" produces a generic article. "Compare HubSpot and Pipedrive for early-stage SaaS startups, focusing on contact enrichment" produces something a real buyer would read. * **Real reference URLs.** We pull these into the writer's context so the article can quote and cite them — citations are one of the things that makes AI assistants more likely to reuse your content. * **Up to 10 keywords.** This is what the SEO step optimizes around. Leaving them blank weakens the metadata. See [Tutorial: Generate articles](/tutorials/generate-articles#tips-for-higher-quality-articles) for more. ## Dashboard and metrics ### Dashboard shows no data after setup finished Don't panic — usually it's a filter issue, not a missing-data issue. 1. **Check the date range.** The default is the **last 7 days**. A project created today only has data for today — make sure today is included. 2. **Clear provider and topic filters.** A filter set to a provider or topic with no data hides everything. 3. **Confirm setup actually finished.** Look at the project status. If it's still `processing`, metrics will populate as it completes. 4. **Wait for the first daily run.** The initial setup collects a starter batch. The full daily collection happens once every 24 hours, so a project bootstrapped late at night may look thin until the next day. ### Visibility, sentiment, or position is showing as 0 A `0` here means **no data**, not "your brand is doing badly." Specifically: * **Visibility 0%** — your brand wasn't mentioned in any responses for this period. Either too little data yet, or the prompts you're tracking don't actually surface your brand. * **Sentiment 0** — your brand wasn't mentioned, so there was nothing to score. (Sentiment runs 0–100; a real "negative" score sits in the 30–50 range.) * **Position 0** — same reason: no mention means no place in the response to measure. If you're seeing zeroes across the board, the action is to widen the date range first, then look at whether your prompts are actually the questions buyers ask in your category. Sometimes the prompts need editing, not the brand. ### My numbers aren't moving day-over-day A few things to confirm: * The initial setup is **complete** (not still `processing`). * At least one prompt is on the **Active** tab. Suggested and inactive prompts aren't collected. * The date range includes today. * Some metrics (competitor rankings, source aggregations) recompute after the daily collection cycle finishes — if you check very early in the morning, today's numbers may not be in yet. ## Team and access ### Team invite email never arrived 1. **Check spam/junk** — most missing invites end up there. 2. Verify the email address on the members page is correct. 3. Invite codes expire after **10 minutes**. Re-send if more time has passed. 4. If the person already has a Topify account with that email, they're added automatically — no invite code needed, just sign in. ### "Usage limit exceeded" You've hit one of your plan's limits. Check **Management > Subscription** for current usage. For limit definitions and how credits accumulate, see [Billing and plans](/platform/billing-and-plans). ### Cannot add more competitors You've hit your plan's competitor limit. Either remove competitors you no longer need from **Preferences > Competitors**, or upgrade. See [Billing and plans](/platform/billing-and-plans). ## Integrations ### Search Console not showing data * Make sure you authorized the **correct Google account** during OAuth — the one that owns the property. * We can only see properties that account has at least Read access to. * Data may take a few minutes to appear after the first sync. ### CMS publish failed * **WordPress / Shopify / Framer**: confirm the API key or OAuth connection is still valid in **Platform > Integrations**. Tokens can expire or be revoked. * Confirm the publishing user has permission to create posts on the target site. * For WordPress, check that the REST API is enabled and not blocked by a security plugin. ## Still need help? Contact us at [info@topify.ai](mailto:info@topify.ai) with: * Your **project name** and the approximate **time** the issue happened * A **screenshot** of any error message * What you were trying to do, and what you expected to see instead We typically respond within one business day. # Tutorial: Generate articles Source: https://docs.topify.ai/tutorials/generate-articles Generate, review, edit, and publish AI-optimized articles. Generate an AI-optimized article, review the output, edit the metadata, and publish it. By the end you'll have a draft article ready to ship to your CMS. **Prerequisites**: A project with bootstrapping complete, plus available generation credits on your plan. See [Billing and plans](/platform/billing-and-plans) for credit limits. Blog and owned-site content is the foundation of GEO — it's the only AI-era brand asset you fully control. Expect **30–60 days** for new articles to translate into a noticeable lift in organic traffic. See [GEO methodology](/concepts/geo-methodology#channel-mix-for-geo) for the full channel-mix breakdown. ## Where article generation lives Open **Optimization > Actions**. This is your home for article work — Topify recommends article tasks based on your project's data, and you can also create your own from scratch. Actions page showing a list of recommended article generation actions. To start an article, click into one of the recommended actions to follow the steps below — or jump to [Create your own](#create-your-own) at the end of the page if you'd rather start from scratch. Unsure about an input? Click **Let AI decide** anywhere it appears and Topify will fill it in for you. You can override later. ## Steps Before you click **Generate**, take a moment on the input config — the angle, references, and outline strategy you set here drive everything downstream: * **Pin a unique angle** -- generic prompts produce generic content. Specify the angle, audience, and tone. * **Cite real references** -- reference URLs get extracted into the writer's context, so the article quotes them with citations AI engines can verify. * **Try a short outline-only first draft** -- if the topic is broad, generate a short outline-only article first and use it to refine your prompt for a longer follow-up. Click **Generate**. Topify queues the article and runs the multi-step pipeline in the background: 1. **Web research** -- finds grounding truth, data points, and supporting evidence for your topic. Takes around 2--3 minutes. 2. Reference extraction 3. File processing 4. Outline generation 5. Drafting (SEO metadata is produced here) 6. GEO optimization (citations, statistics, authoritative tone applied to the draft) 7. Image generation 8. HTML conversion Topify researches your topic from multiple sources before a single word of the draft is written: * **Live web search** -- pulls current statistics, news, and authoritative coverage from across the web, plus the top-ranking pages for each of your keywords so the article matches what already performs in search. * **Your reference URLs** -- every link you provided is fetched and its content is extracted into the writer's context. * **Your uploaded files** -- PDFs, Word docs, spreadsheets, and text files you attached are read and summarized so internal data and proprietary insights are incorporated. * **Your brand context** -- your project's industry, competitors, and positioning are loaded so the article stays on-brand. * **Real community discussions** (forum-style articles only) -- relevant Reddit threads are fetched so the article reflects how people actually talk about the topic. The research step also enforces quality safeguards: * **Recency** -- data from the last 12 months is prioritized; outdated stats are downranked. * **Concrete data only** -- numbers, dates, and specs must be precise. Vague claims are filtered out before they reach the draft. * **Competitor parity** -- for comparison articles, every competitor you specify is researched at equal depth. * **Fact-check pass** -- once the draft is written, the system re-reads it against the research dossier and flags unsupported claims. When research completes, you can review the dossier — key findings and sourced data points — before the article is drafted. Click **Download Research Result** to export it, **Regenerate** to re-run research, or **Generate Outline** to continue. Research result page with key findings and a market context section listing data points and their source links. Generation typically takes 2--5 minutes. You can leave the page; the status updates live when you return. From the research result page, click **Generate Outline** to draft a structured outline. The outline shows H2 sections, H3 subsections, and the data points pulled from research that each section will draw from. Outline page with H2 and H3 headings, each listing the data points sourced from research. Use **Regenerate** to redraft the outline, **Cancel** to go back, or **Approve & Write** to commit and start drafting. The article view shows the rendered content alongside a sidebar with SEO metadata and JSON-LD schema. Use the **View** toggle to switch between rendered and editor modes; **Regenerate** to redraft; **Approve** to commit. Article view with rendered content on the left and a sidebar showing SEO metadata and JSON-LD schema on the right. Topify currently supports publishing to three CMS platforms: **Shopify**, **WordPress**, and **Framer**. If you have one connected, click **Publish** to push the article directly to your CMS. Publish dialog with Shopify, WordPress, and Framer as available CMS targets. See [Integrations](/platform/integrations) to connect a publishing target. ## Create your own If none of the recommended actions matches what you want to write, you can spin up your own from scratch. Click **+ Custom Action** at the top of the Actions page and pick **Write Article**. The Create Action dialog opens with the article configuration form. Create Action dialog showing Write Article, Forum Engagement, Code / Schema Change, and Update Blog Post options. From here, the rest of the flow is identical to the [Steps](#steps) above — submit, approve the outline, review, and publish. ## What's next Let the agent pick topics, draft, and publish — all from chat. Wire up WordPress, Shopify, or Framer for one-click publishing. ## FAQ ### Does a failed generation use a credit? No. Credits are consumed only when an article completes successfully. If a step in the pipeline fails, you can retry the same task at no cost. ### How is this different from just asking ChatGPT to write an article? ChatGPT writes from training data and whatever is in the chat. Topify's pipeline does live web research, fetches your reference URLs and uploaded files, applies GEO techniques (citations, statistics, authoritative tone) that make AI assistants more likely to *quote* the result, and outputs SEO metadata (slug, meta description, JSON-LD) ready for publishing. The goal isn't a one-off blog post — it's an article that earns AI citations. ### Can I edit the article after it's generated? Yes. From the article view, switch to **editor mode** to edit the body content; the SEO metadata sidebar is also editable. Changes save automatically. # Tutorial: Manage prompts Source: https://docs.topify.ai/tutorials/manage-prompts Read the prompts page, accept suggestions, and organize by topic. Curate the prompts Topify tracks for your brand. You'll first learn how to read the prompts page, then accept suggestions and inspect a prompt's analytics. **Prerequisites**: A project with bootstrapping complete. If you don't have one yet, follow [Tutorial: Set up your first project](/quickstart). ## Where prompts live Navigate to **Tracking > Prompts** in the sidebar. Prompts page with Active / Suggested tabs, date range and model filters, search box, Add Prompts button, and a table grouping prompts by topic with AI Volume, Visibility, and Sentiment columns. ## What you see on this page The page has several sections — understanding each makes the rest of the tutorial click. ### Tabs * **Active** -- prompts that Topify sends to AI providers daily and tracks. * **Suggested** -- AI-drafted prompts waiting for your decision. Promote them to Active or reject them. ### Filter bar The bar above the table controls the metric columns on the right: * **Date range** -- the period that Visibility, Sentiment, and the trend deltas are calculated over. * **All Models** -- which AI providers (ChatGPT, Perplexity, Google AI Overview) are included. Switching narrows the metrics to just that provider. * **Search prompt** -- text search across prompt content. * **+ Add Prompts** -- add a custom prompt or import from research. See [Managing prompts](/guides/managing-prompts). ### Topic groups Prompts are grouped by topic. Each topic header shows the topic name and the prompt count (e.g. *AI Search Visibility Tools — 3 prompts*). * Click the **chevron** on the left of a topic row to expand or collapse it. * Click the **topic row itself** to select every prompt under that topic — useful for bulk actions like untracking a whole topic at once. ### Columns The table shows AI Volume, Visibility, and Sentiment for each prompt, with green/red deltas comparing the selected period to the prior one. For full definitions of every metric, see [Brand metrics](/concepts/brand-metrics). ## Steps Switch to the **Suggested** tab. Suggested tab on the prompts page. The **type** column on the right tags each suggestion as either: * **Suggested** -- generated from your brand context. * **URL** -- generated from the [From research](/guides/managing-prompts#from-research) flow. This takes a URL (your blog post, landing page, or any sub-page of your site) and drafts prompts targeting that page so you can track how AI assistants surface it. Click the refresh button to refresh the list. Use the checkboxes and the bulk action bar at the bottom to **Track** (promote to Active) or **Reject** (remove from the list). Click any active prompt to open its detail page. Prompt detail page. From here you can see: * Per-provider metrics (visibility, sentiment, position) * Cited domains and URLs * The actual AI responses that were collected Drill into a domain to see every response that cited it. ## What's next Use prompts as input for AI-optimized article generation. Let the agent suggest prompt edits and topic restructures. ## FAQ ### What makes a prompt "good"? A good prompt is something a real buyer would actually type into ChatGPT or Perplexity — usually a question rather than a keyword string ("what's the best CRM for a 5-person sales team?" rather than "best CRM small team"). Mix intent types (informational, comparison, transactional) so you cover the full buyer journey, not just the bottom of the funnel. ### Can I edit a prompt after activating it? Editing the prompt text would change what we send to AI providers daily and break trend continuity for that prompt, so we don't allow it. Instead, **untrack** the prompt you want to change and **add** a new version. Historical data on the old prompt is preserved.