Skip to content
English - United Kingdom
  • There are no suggestions because the search field is empty.

The Managed SEO Platform

MSP cycle lifecycle: from invoice to applied changes to client report.

What it is

The Managed SEO Platform (MSP) is the system that turns a paid Xero invoice into a billable SEO work cycle, executes technical SEO changes on the client website via a connector plugin, and closes the cycle with a magic-link report for the client. Ops Hub is the brain; connectors (WordPress plugins, Shopify apps, Wix extensions) are dumb pipes.

The cycle lifecycle

  1. A Xero invoice is paid. Zapier (or the MSP reconciliation watchdog) creates a HubSpot ticket with subject [MSP] Client Name -- Month.
  2. The managed-seo-ticket-poller cron (every 15 min) detects the ticket by its [MSP] subject prefix and opens a cycles row in Supabase (status: active).
  3. The msp-cycle-crawls cron (daily 04:10 UTC) kicks an SF crawl for the cycle. Crawl results are parsed into proposed changes rows.
  4. The AM reviews proposed changes in the approval queue (/managed-seo/queue). They can approve individually, bulk-approve, or rewrite-then-approve.
  5. Approved changes are pulled by the client CMS connector via POST /api/managed-seo/connector/changes-pull.
  6. The connector applies the changes and POSTs results back to /api/managed-seo/connector/result-post.
  7. When the AM closes the HubSpot ticket, the onTicketClosed flow runs: issues a magic-link token, generates the cycle-close report at /msp-r/[token], and pings #bristechtonic-jarvis Slack with a preview link.

Cycle status machine

pending --> active --> in_review --> closed (terminal)
                 |              |
             abandoned      abandoned

The cycle status is NOT automatically flipped to closed when the HubSpot ticket closes. Close requires a close_code_id -- a human judgement call. close_codes is a seeded enum table (append-only).

The connector contract

Any client CMS connector must implement three endpoints:

  • Heartbeat ping: called every 15 min by Ops Hub. Response writes to health_pings.
  • Changes pull: POST /api/managed-seo/connector/changes-pull -- returns approved changes for the connector to apply. Bearer token from connector_tokens (SHA-256 hashed in DB).
  • Result post: POST /api/managed-seo/connector/result-post -- reports applied/failed status per change_id.

Phase 1 change types: meta_title, meta_description, schema_inject, alt_text. The connector must implement a circuit breaker: if N consecutive writes fail, stop pulling and alert Ops Hub.

Approval queue

Proposed changes sit in the approval queue at /managed-seo/queue. The AM can: approve or reject individual changes, bulk-approve all proposed changes for a cycle, or rewrite a change in-line then approve. The msp-stale-approvals cron (hourly) posts a Slack alert when proposed changes have been sitting unapproved for more than 24 hours.

Blog drafts (Phase 4)

The msp-cycle-blogs cron (daily 04:30 UTC) creates skeleton blog_drafts rows for each active cycle up to the package-tier entitlement: SEO Max = 4 per cycle, SEO = 2, SEO Lite = 1. These are stubs for the AM to fill via the Skills Launcher.

Magic-link cycle-close reports

The public URL /msp-r/[token] renders the cycle-close report for the client without authentication. Tokens are single-use, 30-day expiry. Admins can re-issue a token from the client detail page if the client loses the link.

Subject prefix convention

Every HubSpot ticket created for the MSP must have its subject starting with [MSP]. The ticket poller skips tickets without this prefix. This discriminates MSP tickets from other tickets in the same To Do Pipeline.

Ops Hub tables used

cycles, changes, platform_users, connector_tokens, crawl_jobs, blog_drafts, magic_link_tokens, health_pings, close_codes. Plus users.msp_member, users.msp_can_approve_changes, users.msp_can_close_cycles flags on the existing users table.