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.
https://www.seoforge.ai/api/v1Your first request#
curl https://www.seoforge.ai/api/v1/projects \
-H "Authorization: Bearer YOUR_API_KEY"{
"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
200with the result underdata. - Responses aren't cached (
Cache-Control: no-store).
Errors#
Every error has the same shape:
{
"error": {
"code": "project_not_found",
"message": "Project not found."
}
}| Status | Common codes |
|---|---|
| 400 | invalid_arguments, invalid_date, invalid_compare_to, invalid_url, nothing_to_update |
| 401 | missing_api_key, invalid_api_key |
| 402 | subscription_required, project_unpaid |
| 403 | insufficient_scope (a write endpoint called with a read-only key) |
| 404 | project_not_found, job_not_found, tool_not_found, content_plan_item_not_found |
| 405 | method_not_allowed. The Allow header lists the methods that work |
| 409 | search_console_not_connected, search_console_reconnect, article_generating, article_exists, status_locked |
| 429 | rate_limited, daily_data_limit |
| 500 | internal_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:
{ "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#
| Method | Path | Returns |
|---|---|---|
| GET | /projects | Your projects, with your role (owner or member) |
| GET | /projects/{projectId} | The project, article counts by status, and tracked keyword count |
| GET | /projects/{projectId}/settings | Brand, audience personas, writing settings (language, country, tone of voice, point of view, global instructions) and linking rules |
Articles#
| Method | Path | Returns |
|---|---|---|
| GET | /projects/{projectId}/articles?status&limit&offset | Articles without their content |
| POST | /projects/{projectId}/articles | Write. 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#
| Method | Path | Returns |
|---|---|---|
| GET | /projects/{projectId}/content-plan?status&from_date&to_date&limit&offset | Plan items |
| POST | /projects/{projectId}/content-plan | Write. 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#
| Method | Path | Returns |
|---|---|---|
| GET | /projects/{projectId}/rank-tracker | Tracked keywords and their latest positions |
| POST | /projects/{projectId}/rank-tracker | Write. Adds keywords: { "keywords": [], "location_code"?, "language_code"?, "device"? } |
| GET | /keywords/{keywordId}/history | Weekly positions, oldest first, up to 52 |
| DELETE | /keywords/{keywordId} | Write. Stops tracking. History is kept |
Search Console#
| Method | Path | Returns |
|---|---|---|
| GET | /projects/{projectId}/article-performance?article_id&start_date&end_date&compare_to&limit | Clicks, impressions, CTR and position for your published articles |
| GET | /projects/{projectId}/search-traffic?start_date&end_date&compare_to&limit | Site-wide trend, top pages and top queries |
| GET | /projects/{projectId}/page-queries?url&start_date&end_date&compare_to&limit | The queries one URL ranks for |
compare_to is previous_period or previous_year.
Google Analytics#
| Method | Path | Returns |
|---|---|---|
| GET | /projects/{projectId}/analytics?start_date&end_date&channel&compare_to | Sessions, engagement, key events and revenue over time |
| GET | /projects/{projectId}/analytics/landing-pages?channel&start_date&end_date&compare_to&sort&limit | Metrics per landing page |
| GET | /projects/{projectId}/analytics/traffic-sources?channel&start_date&end_date&limit | Traffic by source |
| GET | /projects/{projectId}/analytics/key-events?start_date&end_date | Conversions by event |
| GET | /projects/{projectId}/analytics/audience?channel&landing_page&start_date&end_date | Visitor countries, devices and more |
| GET | /projects/{projectId}/analytics/realtime | Visitors in the last 30 minutes |
Jobs#
| Method | Path | Returns |
|---|---|---|
| GET | /jobs/{jobId} | A job's status: running, completed or failed, with its result or error |
Toolset tools#
| Method | Path | Returns |
|---|---|---|
| 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.
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:
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.
