Documentation Index
Fetch the complete documentation index at: https://docs.topify.ai/llms.txt
Use this file to discover all available pages before exploring further.
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 |
curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions?status=suggested" \
-H "X-API-Key: tk_live_..."
resp = client.get(f"/projects/{project_id}/actions", params={"status": "suggested"})
const resp = await fetch(`${BASE}/projects/${projectId}/actions?status=suggested`, { headers });
Response
{
"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 |
curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/actions/{action_id}" \
-H "X-API-Key: tk_live_..."
resp = client.get(f"/projects/{project_id}/actions/{action_id}")
const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}`, { headers });
Response
{
"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 |
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
}'
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"}],
})
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) with status suggested.
Error responses
| Status | Cause |
|---|
400 | Missing required fields or invalid category/priority/execution type |
403 | API key has read-only access |
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 |
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"]
}'
resp = client.post(f"/projects/{project_id}/actions/enrich", json={
"action_type": "new_content",
"title": "Comparison article",
"keywords": ["best project management tools"],
})
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 ---+
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_..."
resp = client.patch(f"/projects/{project_id}/actions/{action_id}/accept")
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) |
403 | API key has read-only access |
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 |
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"
}'
resp = client.post(f"/projects/{project_id}/actions/batch-transition", json={
"ids": ["a1b2c3d4-...", "e5f6g7h8-..."],
"action": "accept",
})
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
{
"success": true,
"data": {
"succeeded": 2,
"failed": 0,
"total": 2
}
}
Error responses
| Status | Cause |
|---|
400 | Empty ids array or invalid action |
403 | API key has read-only access |
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 |
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_..."
resp = client.post(f"/projects/{project_id}/actions/recommend")
const resp = await fetch(`${BASE}/projects/${projectId}/actions/recommend`, {
method: "POST",
headers,
});
Response
{
"success": true,
"data": {
"task_id": "f1e2d3c4-..."
}
}
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 |
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_..."
resp = client.get(f"/projects/{project_id}/actions/tasks/{task_id}")
const resp = await fetch(`${BASE}/projects/${projectId}/actions/tasks/${taskId}`, { headers });
Response
{
"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) |
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_..."
resp = client.post(f"/projects/{project_id}/actions/{action_id}/enrich-content")
const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}/enrich-content`, {
method: "POST",
headers,
});
Response
{
"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": "<h2>Frequently Asked Questions</h2>...",
"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) |
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_..."
resp = client.post(f"/projects/{project_id}/actions/{action_id}/enrich-forum")
const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}/enrich-forum`, {
method: "POST",
headers,
});
Response
{
"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.
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 |
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_..."
resp = client.post(f"/projects/{project_id}/actions/{action_id}/execute")
const resp = await fetch(`${BASE}/projects/${projectId}/actions/${actionId}/execute`, {
method: "POST",
headers,
});
Response (202 Accepted)
{
"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) |
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"
}'
resp = client.post(f"/projects/{project_id}/actions/{action_id}/execute/respond", json={
"workflow_id": "w1x2y3z4-...",
"decision": "approve",
})
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
{
"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 |