Bỏ qua để đến nội dung

REST API

Nội dung này hiện chưa có sẵn bằng ngôn ngữ của bạn.

The worker exposes a flat REST gateway at /api/v1 that wraps the same MCP dispatch path used by Claude Desktop. Use this when integrating from a plain HTTP client (browser fetch, curl, Python, etc.) instead of an MCP-aware client.

The MCP endpoint at /mcp is unchanged and still preferred for MCP-native clients like Claude Desktop. /api/v1 is the convenience layer for everything else.

https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1

Self-hosted workers use their own <slug>.workers.dev or custom domain.

Every request needs a Bearer token in the Authorization header. Tokens are issued from the admin dashboard at /admin/tokens and follow the formats sn_prod_…, sn_dev_…, or oat_… (an OAuth access token).

Authorization: Bearer sn_prod_xxxxxxxxxxxxxxxxxxxx

The master token is for admin endpoints only — do NOT use it on /api/v1.

Open (Access-Control-Allow-Origin: *). Security rests on the Bearer token; cookies are not used.

OpenAPI 3.0 spec. Unauthenticated — discover the shape before you have a token. Import into Postman / Insomnia / Swagger or generate a client SDK.

Terminal window
curl https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1/openapi.json

Quick sanity check — returns the calling token’s id, name, scope, and owner.

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1/whoami
{
"ok": true,
"result": {
"token_id": "tok_abc123",
"name": "Production app",
"allowed_sites": ["viper-template", "viper-ghl"],
"allowed_tools": ["*"],
"owner_user_id": "alice"
}
}

Lists sites the calling token has scope for. Useful to populate a UI dropdown.

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1/sites
{
"ok": true,
"result": {
"sites": [
{ "id": "viper-template", "name": "Viper Template", "platform": "wordpress", "base_url": "https://viper-template.on-forge.com" },
{ "id": "viper-ghl", "name": "Viper HighLevel", "platform": "gohighlevel", "base_url": null }
]
}
}

Lists all tools the token can call — built-in plus dynamic proxy tools (WordPress MCP Adapter, GoHighLevel). Each tool’s input_schema is JSON Schema. Use that when shaping the request body for the next endpoint.

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1/tools
{
"ok": true,
"result": {
"tools": [
{
"name": "contacts_get-contact",
"description": "Get one contact …",
"input_schema": { "type": "object", "properties": { "site_id": , "contactId": }, "required": ["site_id", "contactId"] }
},
]
}
}

Execute a tool. Path param is the tool name (URL-encoded). Body is the arguments object — same shape as the tool’s input_schema. site_id is required for any tool that targets a specific site.

Terminal window
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "site_id": "viper-ghl", "contactId": "abc123" }' \
https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1/tools/contacts_get-contact

Successful calls return:

{ "ok": true, "result": <unwrapped tool output> }

The gateway flattens the MCP content[] shape:

  • If the tool returned a single { type: "text", text } item and the text parses as JSON → result is the parsed object/array.
  • If it’s a plain string → result is the string.
  • Multi-item content → result is the raw MCP content array.

Errors return:

{ "ok": false, "error": { "code": "E_PUBLISH_NOT_ALLOWED", "message": "Site 'viper' does not allow publish-now. …" } }
HTTPMeaningCommon codes
200Success
400Bad inputE_BAD_JSON, E_BAD_INPUT, E_BAD_PATH, E_BAD_ROUTE
401Missing/invalid tokenE_AUTH
403Scope or per-site flag blocks the callE_SCOPE_DENIED, E_FORBIDDEN_OP, E_PUBLISH_NOT_ALLOWED, E_GOHIGHLEVEL_WRITE_NOT_ALLOWED, E_MAIN_BRANCH_LOCKED, E_FULL_REPO_WRITE_NOT_ALLOWED, E_RANKMATH_DISABLED, E_RANKMATH_PLUGIN_REQUIRED, E_SUPER_ADMIN_ONLY
404Tool/site not foundE_TOOL_NOT_FOUND, E_NOT_FOUND
409State conflictE_FILE_EXISTS, E_REPO_NOT_EMPTY, E_SHA_MISMATCH, E_ROUTE_EXISTS, E_BUILDER_REQUIRED, E_GITHUB_NOT_LINKED
413Payload too largeE_FILE_TOO_LARGE, E_BULK_TOO_LARGE, E_BULK_TOO_MANY_FILES
500Tool error / ambiguous routingE_INTERNAL, E_TOOL_ERROR, E_AMBIGUOUS_SITE
502Upstream failureE_UPSTREAM
503Worker mis-configuredE_MISSING_SECRET

WordPress and GoHighLevel sites expose dynamic proxy tools (e.g. elementor-mcp-list-pages, contacts_get-contact). The same tool name can appear on multiple sites the token has scope for. For these tools:

  • The input_schema.site_id field carries an enum listing every site the tool is available on. Match one of those exactly.
  • The tool description ends with (sites: site-a, site-b, …) so you can see the available sites at a glance.
  • site_id is required. Even when only one site exposes the tool, include it explicitly — code paths that auto-pick the only site are fine, but writing site_id makes calls robust if you later add a second site.
  • If you omit site_id AND multiple sites expose the tool, the worker returns E_AMBIGUOUS_SITE (HTTP 500) with the candidate list in the error message. Resend with site_id populated.
{
"ok": false,
"error": {
"code": "E_AMBIGUOUS_SITE",
"message": "Tool 'elementor-mcp-list-pages' is available on multiple sites (charlottewindowtinting, maryland-clean-rides). Pass an explicit site_id in arguments."
}
}

Built-in tools (create_draft, publish_post, init_astro_project, etc.) also take site_id but never carry an enum — they work on any site of the right platform.

const TOKEN = "sn_prod_…";
async function getContact(siteId, contactId) {
const res = await fetch(
"https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1/tools/contacts_get-contact",
{
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ site_id: siteId, contactId }),
}
);
const body = await res.json();
if (!body.ok) {
throw new Error(`${body.error.code}: ${body.error.message}`);
}
return body.result;
}
import os, requests
BASE = "https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1"
TOKEN = os.environ["SN_TOKEN"]
def call_tool(name, args):
r = requests.post(
f"{BASE}/tools/{name}",
headers={"Authorization": f"Bearer {TOKEN}"},
json=args,
timeout=30,
)
body = r.json()
if not body.get("ok"):
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
return body["result"]
contact = call_tool("contacts_get-contact", {
"site_id": "viper-ghl",
"contactId": "abc123",
})
print(contact)
import { fetch } from "undici";
const BASE = process.env.SN_BASE;
const TOKEN = process.env.SN_TOKEN;
export async function createDraft(siteId, title, contentHtml) {
const res = await fetch(`${BASE}/api/v1/tools/create_draft`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ site_id: siteId, title, content_html: contentHtml }),
});
if (res.status === 401) throw new Error("token invalid");
const body = await res.json();
if (!body.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
return body.result; // { id, status: "draft", url, … }
}
Use caseUse
Claude Desktop, Claude.ai connector, Cursor, Continue/mcp directly — the MCP client speaks JSON-RPC for you
Custom web app (React, Vue, …)/api/v1
Mobile app/api/v1
Backend service (Node/Python/Go/PHP)/api/v1
n8n / Zapier / MakeEither — both have native HTTP nodes; n8n v1.104+ also has an MCP node
Postman / Insomnia / curl/api/v1 (use the OpenAPI spec)
  • Wire format: REST/JSON instead of JSON-RPC envelope.
  • Tool result: MCP content[] is unwrapped — single text item is returned as a parsed object/string for ergonomics.
  • Errors: HTTP status code matches the error semantics (4xx/5xx) instead of always returning 200 with isError: true inside.
  • Discovery: GET /api/v1/tools returns one flat list instead of the tools/list JSON-RPC method.
  • CORS: open on /api/v1. /mcp is also CORS-open but the typical MCP client doesn’t run from a browser.

Same guardrails, same scope checks, same audit log. Use either; never need to use both.