REST API
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
/mcpis unchanged and still preferred for MCP-native clients like Claude Desktop./api/v1is the convenience layer for everything else.
Base URL
Section titled “Base URL”https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1Self-hosted workers use their own <slug>.workers.dev or custom domain.
Authentication
Section titled “Authentication”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_xxxxxxxxxxxxxxxxxxxxThe 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.
Endpoints
Section titled “Endpoints”GET /api/v1/openapi.json
Section titled “GET /api/v1/openapi.json”OpenAPI 3.0 spec. Unauthenticated — discover the shape before you have a token. Import into Postman / Insomnia / Swagger or generate a client SDK.
curl https://seo-navigator-mcp.autumn-recipe-cac7.workers.dev/api/v1/openapi.jsonGET /api/v1/whoami
Section titled “GET /api/v1/whoami”Quick sanity check — returns the calling token’s id, name, scope, and owner.
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" }}GET /api/v1/sites
Section titled “GET /api/v1/sites”Lists sites the calling token has scope for. Useful to populate a UI dropdown.
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 } ] }}GET /api/v1/tools
Section titled “GET /api/v1/tools”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.
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"] } }, … ] }}POST /api/v1/tools/{name}
Section titled “POST /api/v1/tools/{name}”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.
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-contactResponse shape
Section titled “Response shape”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 →resultis the parsed object/array. - If it’s a plain string →
resultis the string. - Multi-item content →
resultis the raw MCP content array.
Errors return:
{ "ok": false, "error": { "code": "E_PUBLISH_NOT_ALLOWED", "message": "Site 'viper' does not allow publish-now. …" } }Status codes
Section titled “Status codes”| HTTP | Meaning | Common codes |
|---|---|---|
| 200 | Success | — |
| 400 | Bad input | E_BAD_JSON, E_BAD_INPUT, E_BAD_PATH, E_BAD_ROUTE |
| 401 | Missing/invalid token | E_AUTH |
| 403 | Scope or per-site flag blocks the call | E_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 |
| 404 | Tool/site not found | E_TOOL_NOT_FOUND, E_NOT_FOUND |
| 409 | State conflict | E_FILE_EXISTS, E_REPO_NOT_EMPTY, E_SHA_MISMATCH, E_ROUTE_EXISTS, E_BUILDER_REQUIRED, E_GITHUB_NOT_LINKED |
| 413 | Payload too large | E_FILE_TOO_LARGE, E_BULK_TOO_LARGE, E_BULK_TOO_MANY_FILES |
| 500 | Tool error / ambiguous routing | E_INTERNAL, E_TOOL_ERROR, E_AMBIGUOUS_SITE |
| 502 | Upstream failure | E_UPSTREAM |
| 503 | Worker mis-configured | E_MISSING_SECRET |
Multi-site tools (proxy tools)
Section titled “Multi-site tools (proxy tools)”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_idfield carries anenumlisting 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_idis required. Even when only one site exposes the tool, include it explicitly — code paths that auto-pick the only site are fine, but writingsite_idmakes calls robust if you later add a second site.- If you omit
site_idAND multiple sites expose the tool, the worker returnsE_AMBIGUOUS_SITE(HTTP 500) with the candidate list in the error message. Resend withsite_idpopulated.
{ "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.
Examples
Section titled “Examples”JavaScript (browser fetch)
Section titled “JavaScript (browser fetch)”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;}Python (requests)
Section titled “Python (requests)”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)Node.js (server-side)
Section titled “Node.js (server-side)”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, … }}When to use REST vs MCP
Section titled “When to use REST vs MCP”| Use case | Use |
|---|---|
| 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 / Make | Either — both have native HTTP nodes; n8n v1.104+ also has an MCP node |
| Postman / Insomnia / curl | /api/v1 (use the OpenAPI spec) |
Differences from /mcp
Section titled “Differences from /mcp”- 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: trueinside. - Discovery:
GET /api/v1/toolsreturns one flat list instead of thetools/listJSON-RPC method. - CORS: open on
/api/v1./mcpis 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.