Skip to content

MCP and API

REST API reference

Read and update your SEOForge projects, articles, content plan, rankings and Google data over HTTPS.

The REST API gives your own scripts and tools the same data and actions as the MCP server. It uses the same API keys.

text
https://www.seoforge.ai/api/v1

Your first request#

bash
curl https://www.seoforge.ai/api/v1/projects \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "data": [
    {
      "id": "8c1d…",
      "name": "Acme",
      "website": "https://acme.com",
      "primary_language": "English",
      "is_paid": true,
      "role": "owner"
    }
  ]
}

Use a project's id in the other endpoints.

Requests and responses#

  • Send JSON bodies with Content-Type: application/json.
  • Dates use YYYY-MM-DD.
  • A successful response is 200 with the result under data.
  • Responses aren't cached (Cache-Control: no-store).

Errors#

Every error has the same shape:

json
{
  "error": {
    "code": "project_not_found",
    "message": "Project not found."
  }
}
StatusCommon codes
400invalid_arguments, invalid_date, invalid_compare_to, invalid_url, nothing_to_update
401missing_api_key, invalid_api_key
402subscription_required, project_unpaid
403insufficient_scope (a write endpoint called with a read-only key)
404project_not_found, job_not_found, tool_not_found, content_plan_item_not_found
405method_not_allowed. The Allow header lists the methods that work
409search_console_not_connected, search_console_reconnect, article_generating, article_exists, status_locked
429rate_limited, daily_data_limit
500internal_error

If Google Analytics isn't connected for a project, the analytics endpoints return { "connected": false, "note": "…" } rather than an error.

Pagination#

List endpoints take limit and offset and return:

json
{ "data": { "total": 132, "limit": 25, "offset": 0, "results": [] } }

limit goes up to 100. It defaults to 25 for articles and 50 for the content plan.

Endpoints#

Endpoints marked Write need a Read & write key.

Projects#

MethodPathReturns
GET/projectsYour projects, with your role (owner or member)
GET/projects/{projectId}The project, article counts by status, and tracked keyword count
GET/projects/{projectId}/settingsBrand, audience personas, writing settings (language, country, tone of voice, point of view, global instructions) and linking rules

Articles#

MethodPathReturns
GET/projects/{projectId}/articles?status&limit&offsetArticles without their content
POST/projects/{projectId}/articlesWrite. Starts generating an article. Send { "content_plan_item_id" } or { "keyword", "title"?, "notes"?, "content_length"? }. Returns a job_id and article_id
GET/articles/{articleId}The full article: Markdown content, meta title and description, cover image
PATCH/articles/{articleId}Write. Updates content, title, meta_title or meta_description. The previous version is saved

content_length is one of 1000-2000, 2000-3000, 3000-4000 or 4000-5000 words.

Content plan#

MethodPathReturns
GET/projects/{projectId}/content-plan?status&from_date&to_date&limit&offsetPlan items
POST/projects/{projectId}/content-planWrite. Adds an item: keyword, plus optional title, scheduled_date, priority (0–100), notes, content_type, content_length
PATCH/content-plan/{itemId}Write. Updates an item. status can be set to planned or skipped

Rank tracking#

MethodPathReturns
GET/projects/{projectId}/rank-trackerTracked keywords and their latest positions
POST/projects/{projectId}/rank-trackerWrite. Adds keywords: { "keywords": [], "location_code"?, "language_code"?, "device"? }
GET/keywords/{keywordId}/historyWeekly positions, oldest first, up to 52
DELETE/keywords/{keywordId}Write. Stops tracking. History is kept

Search Console#

MethodPathReturns
GET/projects/{projectId}/article-performance?article_id&start_date&end_date&compare_to&limitClicks, impressions, CTR and position for your published articles
GET/projects/{projectId}/search-traffic?start_date&end_date&compare_to&limitSite-wide trend, top pages and top queries
GET/projects/{projectId}/page-queries?url&start_date&end_date&compare_to&limitThe queries one URL ranks for

compare_to is previous_period or previous_year.

Google Analytics#

MethodPathReturns
GET/projects/{projectId}/analytics?start_date&end_date&channel&compare_toSessions, engagement, key events and revenue over time
GET/projects/{projectId}/analytics/landing-pages?channel&start_date&end_date&compare_to&sort&limitMetrics per landing page
GET/projects/{projectId}/analytics/traffic-sources?channel&start_date&end_date&limitTraffic by source
GET/projects/{projectId}/analytics/key-events?start_date&end_dateConversions by event
GET/projects/{projectId}/analytics/audience?channel&landing_page&start_date&end_dateVisitor countries, devices and more
GET/projects/{projectId}/analytics/realtimeVisitors in the last 30 minutes

Jobs#

MethodPathReturns
GET/jobs/{jobId}A job's status: running, completed or failed, with its result or error

Toolset tools#

MethodPathReturns
GET/tools?toolset=Every toolset tool with its description and arguments
POST/tools/{name}Runs a tool. The JSON body holds its arguments

The tool reference lists each tool. The core MCP tools, like list_projects, aren't available here; use the endpoints above instead.

bash
curl -X POST https://www.seoforge.ai/api/v1/tools/find_striking_distance \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"project_id": "YOUR_PROJECT_ID"}'

Tool responses include meta.cost, which shows what the call would cost in credits. Paid data is free during the beta, up to a daily limit.

Long-running jobs#

Generating an article, refreshing one, running a site audit and batch search lookups take minutes, so they return a job_id straight away. Poll the job until it finishes:

bash
curl https://www.seoforge.ai/api/v1/jobs/JOB_ID \
  -H "Authorization: Bearer YOUR_API_KEY"

While a job runs, the response includes a stage. For articles, that's the current writing step. Article generation usually takes several minutes, and a refresh takes 1–3 minutes. Polling every 15–30 seconds is plenty. There are no completion callbacks.

Rate limits#

60 requests per minute per key. Going over returns 429 rate_limited with Retry-After: 60. See API keys and limits for the rest.