# CTRify MCP and REST API Canonical documentation: https://www.ctrify.com/developers OpenAPI 3.0 contract: https://www.ctrify.com/api/ctrify/v1/openapi.json Documentation version: 2026-09-30 CTRify is a hosted SEO action platform. Customers connect an AI agent through MCP or integrate their own software through the REST API. They do not install CTRify, PHP, Python, MySQL or AWS infrastructure locally. ## Canonical entry points - MCP server: `https://www.ctrify.com/mcp` - REST API base: `https://www.ctrify.com/api/ctrify/v1` - OpenAPI: `https://www.ctrify.com/api/ctrify/v1/openapi.json` - OAuth protected-resource metadata: `https://www.ctrify.com/.well-known/oauth-protected-resource/mcp` - OAuth authorization-server metadata: `https://www.ctrify.com/.well-known/oauth-authorization-server` - Dynamic client registration: `https://www.ctrify.com/oauth/register` - Authorization: `https://www.ctrify.com/oauth/authorize` - Token exchange and rotation: `https://www.ctrify.com/oauth/token` - Token revocation: `https://www.ctrify.com/oauth/revoke` The entry-point URLs are not web pages. - `GET https://www.ctrify.com/mcp` correctly returns HTTP 405 with `Standalone SSE is not enabled`. CTRify MCP uses Streamable HTTP with JSON-RPC requests over `POST`. Configure the URL in an MCP client instead of opening it in a browser. - `GET https://www.ctrify.com/api/ctrify/v1` correctly returns HTTP 404 `route_not_found`. The value is a base namespace, not an API resource. Append a documented path or use `/openapi.json`. Do not reinterpret either strict error as a successful connection. ## Choose MCP or REST Use MCP when a person wants Cursor, ChatGPT or another compatible agent to work with CTRify in natural language. Use REST for applications, scheduled integrations, dashboards and deterministic programmatic workflows. Both interfaces use the same OAuth grants, authenticated customer identity, ownership checks, reviewed operation catalog, usage billing, audit trail, idempotency controls and native CTRify behavior. ## MCP connection Required client capabilities: - Remote MCP over Streamable HTTP. - OAuth protected-resource and authorization-server discovery. - Authorization code with PKCE `S256`. - Dynamic public-client registration or an already registered public client. - Bearer authentication. ### Cursor Add this to `.cursor/mcp.json` for one project or to Cursor's global MCP configuration: ```json { "mcpServers": { "ctrify": { "url": "https://www.ctrify.com/mcp" } } } ``` Then connect CTRify from Cursor's MCP settings. Cursor should open the CTRify login and OAuth consent flow automatically. Review the exact permissions before approval. Cursor Agent CLI can use: ```text cursor-agent mcp list cursor-agent mcp login ctrify cursor-agent mcp list-tools ctrify ``` Current Cursor MCP documentation: https://cursor.com/docs/context/mcp ### Codex Desktop and CLI Codex Desktop and the Codex CLI share the same MCP configuration. Register CTRify as a Streamable HTTP server and start OAuth from a terminal: ```text codex mcp add ctrify --url https://www.ctrify.com/mcp codex mcp login ctrify ``` Keep the `codex mcp login ctrify` command running while the browser opens CTRify. Sign in, approve the requested permissions, and let the browser return to the new `http://127.0.0.1:/callback/...` address created by Codex. Use only the consent tab opened by the current command; an older tab belongs to an expired login attempt. The one-time CTRify external-access activation is account-wide, so retrying OAuth or connecting another compatible client does not create a second activation charge. After approval, verify the connection with: ```text codex mcp list ``` In Codex Desktop, the equivalent flow is available from Settings, MCP servers: add a Streamable HTTP server with the canonical CTRify URL and select Authenticate. Current official Codex MCP documentation: https://developers.openai.com/codex/mcp ### ChatGPT The current ChatGPT developer-mode flow is: 1. In ChatGPT on the web, open Settings, then Security and login, and enable Developer mode. 2. Open ChatGPT Plugins and select the plus button to create a developer-mode app. 3. Choose a remote MCP connection. 4. Enter `https://www.ctrify.com/mcp` and use OAuth. 5. Complete CTRify login and consent. 6. Review the discovered tools and add CTRify from Developer mode tools in a conversation. Availability depends on the ChatGPT account and workspace policy. OpenAI's interface and eligibility can change. Current official OpenAI documentation: https://developers.openai.com/api/docs/guides/developer-mode ## MCP tools and required workflow CTRify exposes a small stable tool surface backed by 513 allowlisted operations in 25 product modules: - `ctrify_capabilities_search`: bounded search of operations available to the authenticated grant. - `ctrify_capability_get`: exact input contract, permissions, cost behavior, execution mode and side effects for one `operation_id`. - `ctrify_action_execute`: execute only that selected allowlisted operation. - `ctrify_feedback_submit`: report the final outcome of a user intent, including missing capabilities, blocked or partial work, bugs, confusing contracts and incorrect results. - `ctrify_account_snapshot`: account summary. - `ctrify_mcp_usage_get`: activation and external-access usage. - `ctrify_websites_list`: paginated owned website list. - `ctrify_website_get`: one owned website. - `ctrify_seo_opportunities_analyze`: read-only SEO opportunity audit. An agent must follow this sequence for catalog operations: 1. Search once for each new goal with `ctrify_capabilities_search`. Put the actual desired outcome in the optional `intent` field (10–500 characters) and concise function keywords in `query`, or specify a module. Keep `intent` unchanged when reformulating `query`. CTRify returns an `intent_ref` for that search. 2. Read the exact contract with `ctrify_capability_get` and pass the same `intent_ref`. 3. Inspect permissions, arguments, defaults, cost behavior, side effects and whether execution is immediate or asynchronous. 4. Execute with `ctrify_action_execute` only when arguments match the contract, again passing the same `intent_ref`. 5. For every write, job or destructive operation, supply a unique printable `idempotency_key` between 8 and 200 characters. 6. Read the returned state. A successful queue response does not imply the native asynchronous job already finished. 7. If the goal cannot be completed, is only partially completed, produces an incorrect result or exposes a documentation/contract problem, call `ctrify_feedback_submit` with the original intent, completion state, attempted operation and `intent_ref`. CTRify records the bounded text supplied to capability search, the operations offered, the selected contract, the execution outcome and any explicit feedback. This lets CTRify identify what customers and their agents actually tried to accomplish and whether the current product could do it. Obvious credentials accidentally included in intent text are replaced with `[redacted]`; ordinary product intent is retained. Feedback submission uses zero external-access points but is rate limited against abuse. For example, send `{"intent":"Show my recent Viral campaigns and identify failed jobs","query":"campaign activity","module":"ux_signals","kind":"read"}`. The real goal must describe the requested functionality, not just repeat the operation name. An empty result is an unmet search to review, not proof that a feature is absent; report confirmed gaps through `ctrify_feedback_submit` with the missing behavior and observed outcome. Continue passing `intent_ref` explicitly. If a client omits it, CTRify can infer an audit association from a recent unambiguous search on the same customer/token/client whose results offered the selected operation. Inferred associations are labeled and ambiguous requests are not guessed. Contract lookup and execution also accept an optional top-level `intent` for cached operations; it never becomes a native action argument. Execution success, asynchronous acceptance and the agent's report of goal completion are recorded separately. CTRify cannot see a conversation or goal the client never sends. Never pass or infer `customer_id`. CTRify derives it from the bearer token. Never infer that a mutation completed from a read response. Never retry changed mutation arguments under the same idempotency key. Native CTRify behavior is authoritative: immediate actions stay immediate, queued workflows stay asynchronous, and existing ownership, state, price, provider and product checks remain active. MCP does not add a synthetic prepare/apply workflow. ### Viral Organic Boost Viral campaigns can be created from manually supplied URLs. The site does not need to be verified and Search Console is not required. `ux_signals.viral.search_console.sites.list` and `ux_signals.viral.targets.list` are optional discovery helpers only. 1. Call `ux_signals.viral.campaign.quote` with `site_domain`, `campaign_country` and 1–10 `target_urls`. At least one URL must belong to `site_domain`; additional backlink URLs may be external. 2. Present the returned per-URL and total CTRify credit cost. 3. Call `ux_signals.viral.campaign.create` with the same arguments and `expected_quote_hash`, plus a transport idempotency key. 4. Monitor the returned campaign through `ux_signals.campaign.get`, `ux_signals.campaign.tasks.list` and the other UX Signals reads. Creation preserves CTRify's native Viral workflow: short-link preparation is asynchronous, credits are charged by the existing native flow when preparation completes, and the existing scheduler performs the campaign. ### Local Grid GMB Signals GMB Signals campaigns can be created only from completed Local Grid runs owned by the authenticated customer. External pins, coordinates, route instructions and route links are not accepted or returned. 1. Call `local_grid.gmb_signals.sources.get` for one completed `run_ref` to discover compatible completed keyword runs. 2. Call `local_grid.gmb_signals.campaign.quote` with the selected `source_run_refs`, `daily_actions` and `campaign_days`. 3. Call `local_grid.gmb_signals.campaign.create` with the same values, `expected_quote_hash`, a native `campaign_idempotency_key`, and a transport idempotency key. 4. Call `local_grid.gmb_signals.campaign.get` to read campaign state, source keywords and aggregate result counts. CTRify validates ownership, completed source state, target and grid compatibility, eligible Local Grid evidence, credits and idempotency. CTRify chooses execution locations internally and does not expose its route-signal mechanics. ## REST API routes Base all paths below on `https://www.ctrify.com/api/ctrify/v1`. | Method | Path | Purpose | | --- | --- | --- | | `GET` | `/openapi.json` | Public OpenAPI 3.0 contract; no token or usage charge | | `GET` | `/account` | Authenticated account snapshot | | `GET` | `/usage` | Activation, included usage and reusable paid points | | `GET` | `/websites` | Paginated list of owned websites | | `GET` | `/websites/{site_id}` | One owned website's status and metrics | | `POST` | `/seo/audits` | Read-only SEO opportunity audit | | `POST` | `/feedback` | Record an intent outcome, missing capability or external-access problem | | `GET` | `/capabilities` | Bounded catalog search; supply `query`, `module` or `kind` | | `GET` | `/capabilities/{operation_id}` | Exact operation contract | | `POST` | `/operations/{operation_id}` | Execute one allowlisted operation | ### List websites ```bash curl -sS \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ 'https://www.ctrify.com/api/ctrify/v1/websites?limit=25' ``` ### Search the catalog and get a contract ```bash curl -sS \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ 'https://www.ctrify.com/api/ctrify/v1/capabilities?query=author&kind=read&limit=10' curl -sS \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ 'https://www.ctrify.com/api/ctrify/v1/capabilities/authors.list?intent_ref=INTENT_REF_FROM_SEARCH' ``` Keep the `data.intent_ref` returned by the catalog search. Send it as the `intent_ref` query parameter when reading a contract and as the `CTRify-Intent-Ref` header when executing an operation. ### Execute a read operation ```bash curl -sS -X POST \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ -H 'Content-Type: application/json' \ -H 'CTRify-Intent-Ref: INTENT_REF_FROM_SEARCH' \ --data '{"limit":20,"status":"active"}' \ 'https://www.ctrify.com/api/ctrify/v1/operations/authors.list' ``` ### Execute a mutation ```bash curl -sS -X POST \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: author-create-20260815-0001' \ -H 'CTRify-Intent-Ref: INTENT_REF_FROM_SEARCH' \ --data '{"firstname":"Ada","lastname":"Example"}' \ 'https://www.ctrify.com/api/ctrify/v1/operations/authors.create' ``` The JSON body contains only the selected operation's arguments. Do not put `operation_id`, `customer_id` or an idempotency key in the body. The operation ID comes from the path; mutations take the idempotency key from the `Idempotency-Key` header. ### Report a blocked or incomplete intent ```bash curl -sS -X POST \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ -H 'Content-Type: application/json' \ --data '{"category":"missing_capability","completion_state":"blocked","intent":"Schedule a generated-site report every Monday","intent_ref":"INTENT_REF_FROM_SEARCH"}' \ 'https://www.ctrify.com/api/ctrify/v1/feedback' ``` ## OAuth for REST clients Use a maintained OAuth library where possible. CTRify supports public clients with authorization code plus PKCE `S256`; there is no reusable client secret. Access tokens last one hour. Refresh tokens last up to 30 days and rotate on every successful use. Store the newly returned refresh token before discarding the old one. Reuse of an already rotated refresh token revokes the active grant family. ### Register a public client ```bash curl -sS -X POST \ -H 'Content-Type: application/json' \ --data '{ "client_name":"Your CTRify integration", "redirect_uris":["http://127.0.0.1:8765/callback"], "application_type":"native", "grant_types":["authorization_code","refresh_token"], "response_types":["code"], "token_endpoint_auth_method":"none" }' \ 'https://www.ctrify.com/oauth/register' ``` HTTPS redirect URIs are accepted. HTTP redirect URIs are accepted only for `localhost`, `127.0.0.1` or `::1` loopback clients. ### Open the authorization request Generate a random PKCE verifier of 43 to 128 allowed characters, its SHA-256 base64url challenge, and a random state value. Open: ```text https://www.ctrify.com/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=YOUR_URL_ENCODED_REDIRECT_URI &scope=account%3Aread%20websites%3Aread%20assets%3Aread%20seo%3Aanalyze &state=YOUR_RANDOM_STATE &code_challenge=YOUR_BASE64URL_SHA256_CHALLENGE &code_challenge_method=S256 &resource=https%3A%2F%2Fwww.ctrify.com%2Fmcp ``` The CTRify user signs in, reviews the exact permissions and approves or denies access. Validate the returned `state` before using the returned one-time code. ### Exchange the code ```bash curl -sS -X POST \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode 'client_id=YOUR_CLIENT_ID' \ --data-urlencode 'code=THE_RETURNED_CODE' \ --data-urlencode 'redirect_uri=http://127.0.0.1:8765/callback' \ --data-urlencode 'code_verifier=YOUR_ORIGINAL_PKCE_VERIFIER' \ --data-urlencode 'resource=https://www.ctrify.com/mcp' \ 'https://www.ctrify.com/oauth/token' ``` ### Rotate a refresh token ```bash curl -sS -X POST \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=refresh_token' \ --data-urlencode 'client_id=YOUR_CLIENT_ID' \ --data-urlencode 'refresh_token=YOUR_CURRENT_REFRESH_TOKEN' \ --data-urlencode 'resource=https://www.ctrify.com/mcp' \ 'https://www.ctrify.com/oauth/token' ``` ## OAuth scopes When `scope` is omitted, CTRify grants only `account:read websites:read assets:read seo:analyze`. | Scope | Permission | | --- | --- | | `account:read` | Read account state and setup | | `websites:read` | Read owned websites and metrics | | `seo:audit` | Run the dedicated read-only SEO audit | | `assets:read` | Read owned assets, content, campaigns, jobs and reports | | `assets:write` | Edit or publish supported owned assets | | `seo:analyze` | Run analyses and plans without mutations | | `seo:execute` | Run SEO workflows, queues and native credit-using actions | | `connections:manage` | Manage supported connections | | `billing:read` | Read plans, credits and invoices | | `billing:manage` | Run supported billing-management operations | | `domains:manage` | Run registration, DNS and nameserver workflows | | `outreach:send` | Connect delivery providers and send or schedule outreach | | `destructive:execute` | Delete, stop or irreversibly roll back supported assets | Write, execution, connection, billing, domain, outreach and destructive scopes must be requested explicitly and appear on the consent screen. ## Activation and external usage billing MCP and REST access is available to active CTRify customers regardless of subscription type. Subscription plans continue to determine the normal price at which the customer buys CTRify credits. - The first approved external connection charges 100 CTRify credits once per customer and activates both MCP and API access. - Later compatible clients do not create another activation charge for that same account. - Each customer receives 100 included external-access points per UTC day. - After the included allowance, one CTRify credit purchases 1,000 reusable external-access points. - Small indexed reads normally use very few points. Wider lists, live audits, repeated polling and bulk work scale with the requested or returned work. - Handled failed requests consume only anti-abuse overhead. Internal CTRify failures consume no usage points. - Operations with a native CTRify price retain it and receive only external orchestration overhead; the native work is not priced twice. Usage is enforced, not simulated or shadow billed. Every completed result includes usage metadata. Use MCP `ctrify_mcp_usage_get` or REST `GET /usage` to inspect it. ## Security constraints - The authenticated token selects the customer. Clients cannot select or override `customer_id`. - Only reviewed catalog operations can run. - No public route accepts arbitrary PHP method names, SQL, cron callbacks, filesystem paths or internal object-storage keys. - Catalog search is bounded and returns summaries; exact schemas are retrieved one operation at a time. - Capability searches retain the real bounded intent text and correlate it with offered operations and later outcomes. Explicit feedback retains the stated intent and completion state for product review. Obvious credentials are redacted; ordinary intent text is not discarded. - Native ownership, availability, state, provider and price checks remain active. - Mutations require idempotency protection. - Access and refresh tokens are stored by CTRify only as hashes. - Never put raw tokens in URLs, prompts, source control or logs. - Rate limits, catalog-enumeration controls, usage metering, auditing and grant revocation apply to all plans. ## REST response model Success: ```json { "data": {}, "meta": { "request_id": "...", "api_version": "2026-09-28", "mcp_request_id": "...", "usage": {} } } ``` Error: ```json { "error": { "code": "invalid_arguments", "message": "The request or operation arguments are invalid." }, "request_id": "..." } ``` | Status | Meaning | | --- | --- | | `400` | Malformed request, path or protocol input | | `401` | Missing, expired or invalid bearer token | | `402` | Additional CTRify credits required for external usage | | `403` | Missing scope or inactive access | | `404` | Missing route, resource or owned object | | `405` | Existing route called with an unsupported HTTP method | | `409` | Idempotency key conflicts with an earlier request | | `422` | Query, JSON body or operation arguments violate the contract | | `429` | Request, heavy-operation or enumeration rate limit reached | | `503` | CTRify failed closed because a required metering/runtime dependency was unavailable | The live OpenAPI and OAuth metadata are authoritative for route and authorization discovery.