# Context Sites — tools

44 tools · contract `blog-1.3.0` · published 2026-11-06 from `b22a1da` · registered `96ec363`

The publishing engine for Context: agents draft, lint and publish blog posts, site pages and Instagram carousels on the user's own domain, with a person approving every piece before it goes live. Also drives the AEO/GEO loop — tracking AI-referral traffic, Search Console queries and an AI-visibility probe so the agent can see and improve how often assistants cite the site.

Endpoint: `https://sites.onecontext.me/api/mcp` · auth: oauth2 (Google) · first call: `setup` (or say "Call the Context Sites tool `setup` with workflow 'blog' and follow its nextAction.")

Machine catalog: [tools.json](https://agents.onecontext.me/extensions/context-sites/tools/context-blog/tools.json) · live server card: https://sites.onecontext.me/.well-known/mcp/server-card.json

- **claude-ai**: Settings → Connectors → Add custom connector → name Context Sites, URL https://sites.onecontext.me/api/mcp
- **chatgpt**: Settings → Connectors → Add → URL https://sites.onecontext.me/api/mcp
- **claude-code**: `claude mcp add --transport http context-blog https://sites.onecontext.me/api/mcp`
- **cursor**: Add an HTTP MCP server with URL https://sites.onecontext.me/api/mcp

Skills that wrap this server: [rules](https://agents.onecontext.me/skills/rules) (base), [setup-context](https://agents.onecontext.me/skills/setup-context) (base), [grill-me](https://agents.onecontext.me/skills/grill-me) (base), [wayfinder](https://agents.onecontext.me/skills/wayfinder) (base), [daily-brief](https://agents.onecontext.me/skills/daily-brief) (base), [rules-blog](https://agents.onecontext.me/extensions/context-sites/skills/rules-blog) (base), [blog-agent](https://agents.onecontext.me/extensions/context-sites/skills/blog-agent) (workflow), [site-builder](https://agents.onecontext.me/extensions/context-sites/skills/site-builder) (workflow), [instagram-drafter](https://agents.onecontext.me/extensions/context-sites/skills/instagram-drafter) (workflow)

## Skills & setup (5 · all)

### `usage_guide` — read-only

**Usage guide.** Read this first — what Context Blog is, how it relates to the Context MCP (approvals live there), tools, tiers and rules.

Input schema:

```json
{
 "type": "object",
 "properties": {},
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "guide": {
     "type": "string"
    }
   },
   "required": [
    "guide"
   ],
   "additionalProperties": false
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "type": "object",
     "additionalProperties": {}
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `get_capabilities` — read-only

**Get capabilities.** Machine-readable Context Blog contract: version, tools, workflows, harnesses, auth methods, limits and the server card URL (the full tool catalog with schemas, unauthenticated).

Input schema:

```json
{
 "type": "object",
 "properties": {},
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "contractVersion": {
     "type": "string"
    },
    "product": {
     "type": "string"
    },
    "tools": {
     "type": "array",
     "items": {
      "type": "string"
     }
    },
    "workflows": {
     "type": "array",
     "items": {
      "type": "string"
     }
    },
    "harnesses": {
     "type": "array",
     "items": {
      "type": "string"
     }
    },
    "harnessSkills": {
     "type": "array",
     "items": {
      "type": "string"
     }
    },
    "skillChannel": {
     "type": "string"
    },
    "serverCard": {
     "type": "string"
    },
    "features": {
     "type": "object",
     "additionalProperties": {}
    },
    "auth": {
     "$ref": "#/properties/data/properties/features"
    },
    "limits": {
     "type": "string"
    }
   },
   "required": [
    "contractVersion",
    "product",
    "tools",
    "workflows",
    "harnesses",
    "harnessSkills",
    "skillChannel",
    "serverCard",
    "features",
    "auth",
    "limits"
   ],
   "additionalProperties": false
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data/properties/features"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `list_skill_catalog` — read-only

**List skill catalog.** Active skills on the shared skill channel (key, kind, version, name, description, depends, product, url).

Input schema:

```json
{
 "type": "object",
 "properties": {},
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "skills": {
     "type": "array",
     "items": {
      "type": "object",
      "additionalProperties": {}
     }
    }
   },
   "required": [
    "skills"
   ],
   "additionalProperties": false
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data/properties/skills/items"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `get_skill` — read-only

**Get skill.** One skill revision (active by default) with its SKILL.md content, extra files and provenance.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "key": {
   "type": "string",
   "minLength": 1,
   "maxLength": 64,
   "pattern": "^[a-z0-9][a-z0-9-]*$"
  },
  "version": {
   "type": "integer",
   "exclusiveMinimum": 0
  }
 },
 "required": [
  "key"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "skill": {
     "type": "object",
     "additionalProperties": {}
    }
   },
   "required": [
    "skill"
   ],
   "additionalProperties": false
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data/properties/skill"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `setup` — read-only

**Setup.** CALL THIS FIRST in every session (agent-setup/1). Resolves the workflow's skill (blog-agent / site-builder / instagram-drafter) plus `rules-blog` and dependencies on a skill channel (`channel`: latest | beta | vN); harness skills (rules, setup-context, wayfinder, grill-me, daily-brief) are included unless listed in `installedSkills`. Pass `skills_held` / `guide_held_sha256` (what you have on disk) to get per-skill `status` (current | update | install), `drift`, `nextAction` and `install.commands`; `include:"bodies"` inlines the Agent guide and every SKILL.md for chat hosts. Also returns `mode:"files"` (paths per harness + an AGENTS.md block) or `mode:"chat"` (public skill links + inline protocol) with `nextStep`. Idempotent, no side effects.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "harness": {
   "type": "string",
   "enum": [
    "claude-code",
    "codex",
    "cursor",
    "generic",
    "chat"
   ]
  },
  "canWriteFiles": {
   "type": "boolean"
  },
  "workflow": {
   "type": "string",
   "enum": [
    "blog",
    "site",
    "instagram"
   ]
  },
  "installedSkills": {
   "type": "array",
   "items": {
    "type": "string"
   },
   "maxItems": 64
  },
  "caller": {
   "type": "object",
   "properties": {
    "agent": {
     "type": "string",
     "maxLength": 120
    },
    "model": {
     "type": "string",
     "maxLength": 120
    }
   },
   "additionalProperties": false
  },
  "host": {
   "type": "string",
   "minLength": 1,
   "maxLength": 64,
   "description": "Known: claude-code, codex, cursor, claude-ai, chatgpt, opencode, gemini-cli, generic, chat; any other name is a file-capable agent (generic, agent_install)."
  },
  "channel": {
   "type": "string",
   "pattern": "^(latest|beta|v[1-9]\\d*)$"
  },
  "include": {
   "type": "string",
   "enum": [
    "manifest",
    "bodies"
   ]
  },
  "skills_held": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64
     },
     "sha256": {
      "type": "string",
      "pattern": "^[a-f0-9]{64}$"
     }
    },
    "required": [
     "key",
     "sha256"
    ],
    "additionalProperties": false
   },
   "maxItems": 64
  },
  "guide_held_sha256": {
   "type": "string",
   "pattern": "^[a-f0-9]{64}$"
  },
  "install_scope": {
   "type": "string",
   "enum": [
    "user",
    "project"
   ]
  }
 },
 "required": [
  "workflow"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "schema": {
     "type": "string",
     "const": "agent-setup/1"
    },
    "server": {
     "type": "object",
     "properties": {
      "key": {
       "type": "string"
      },
      "name": {
       "type": "string"
      },
      "product": {
       "type": "string"
      },
      "endpoint": {
       "type": "string"
      },
      "contractVersion": {
       "type": "string"
      },
      "catalog": {
       "type": "object",
       "properties": {
        "url": {
         "type": "string"
        },
        "publishedAt": {
         "type": "string"
        },
        "sourceCommit": {
         "type": "string"
        },
        "toolCount": {
         "type": "number"
        }
       },
       "required": [
        "url",
        "publishedAt",
        "sourceCommit",
        "toolCount"
       ],
       "additionalProperties": false
      }
     },
     "required": [
      "key",
      "name",
      "product",
      "endpoint",
      "contractVersion",
      "catalog"
     ],
     "additionalProperties": false
    },
    "channel": {
     "type": "object",
     "properties": {
      "name": {
       "type": "string"
      },
      "ref": {
       "type": "string"
      },
      "sha": {
       "type": [
        "string",
        "null"
       ]
      },
      "date": {
       "type": [
        "string",
        "null"
       ]
      }
     },
     "required": [
      "name",
      "ref",
      "sha",
      "date"
     ],
     "additionalProperties": false
    },
    "guide": {
     "type": "object",
     "properties": {
      "url": {
       "type": "string"
      },
      "sha256": {
       "type": [
        "string",
        "null"
       ]
      },
      "target": {
       "type": "string"
      },
      "status": {
       "type": "string",
       "enum": [
        "current",
        "update",
        "install"
       ]
      }
     },
     "required": [
      "url",
      "sha256",
      "target",
      "status"
     ],
     "additionalProperties": false
    },
    "skills": {
     "type": "array",
     "items": {
      "type": "object",
      "properties": {
       "key": {
        "type": "string"
       },
       "product": {
        "type": "string"
       },
       "version": {
        "type": "number"
       },
       "sha256": {
        "type": "string"
       },
       "url": {
        "type": "string"
       },
       "target": {
        "type": "string"
       },
       "status": {
        "$ref": "#/properties/data/properties/guide/properties/status"
       },
       "files": {
        "type": "array",
        "items": {
         "type": "object",
         "properties": {
          "path": {
           "type": "string"
          },
          "url": {
           "type": "string"
          },
          "sha256": {
           "type": "string"
          }
         },
         "required": [
          "path",
          "url",
          "sha256"
         ],
         "additionalProperties": false
        }
       }
      },
      "required": [
       "key",
       "product",
       "version",
       "sha256",
       "url",
       "target",
       "status",
       "files"
      ],
      "additionalProperties": false
     }
    },
    "drift": {
     "type": "boolean"
    },
    "nextAction": {
     "type": "string",
     "enum": [
      "continue",
      "install_skills",
      "update_skills",
      "human_upload"
     ]
    },
    "install": {
     "type": "object",
     "properties": {
      "mode": {
       "type": "string",
       "enum": [
        "agent_install",
        "human_upload"
       ]
      },
      "commands": {
       "type": "array",
       "items": {
        "type": "string"
       }
      }
     },
     "required": [
      "mode",
      "commands"
     ],
     "additionalProperties": false
    },
    "bodies": {
     "type": "object",
     "properties": {
      "guide": {
       "type": [
        "string",
        "null"
       ]
      },
      "skills": {
       "type": "object",
       "additionalProperties": {
        "type": "string"
       }
      }
     },
     "required": [
      "guide",
      "skills"
     ],
     "additionalProperties": false
    },
    "mode": {
     "type": "string",
     "enum": [
      "chat",
      "files"
     ]
    },
    "nextStep": {
     "type": "string"
    }
   },
   "required": [
    "schema",
    "server",
    "channel",
    "guide",
    "skills",
    "drift",
    "nextAction",
    "install",
    "mode",
    "nextStep"
   ],
   "additionalProperties": true
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "type": "object",
     "additionalProperties": {}
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

## Checker (4 · all)

### `preview_render`

**Render preview.** Server-side render of a draft article or page into one self-contained HTML page with the tenant's design tokens as CSS variables. Stores it for 7 days and returns a signed, expiring URL (GET /preview/<id>?sig=…) plus the HTML — attach the HTML to the Context issue as docKind "preview". Caller must be a member of tenant_slug (a tenant with no members yet accepts any signed-in caller).

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "kind": {
   "type": "string",
   "enum": [
    "article",
    "page"
   ]
  },
  "title": {
   "type": "string",
   "minLength": 1,
   "maxLength": 300
  },
  "description": {
   "type": "string",
   "minLength": 1,
   "maxLength": 1000
  },
  "sections": {
   "type": "array",
   "items": {
    "anyOf": [
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "paragraph"
       },
       "text": {
        "type": "string",
        "minLength": 1,
        "maxLength": 20000
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "heading"
       },
       "level": {
        "type": "number",
        "enum": [
         2,
         3
        ]
       },
       "text": {
        "type": "string",
        "minLength": 1,
        "maxLength": 300
       }
      },
      "required": [
       "type",
       "level",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "list"
       },
       "ordered": {
        "type": "boolean"
       },
       "items": {
        "type": "array",
        "items": {
         "$ref": "#/properties/sections/items/anyOf/0/properties/text"
        },
        "minItems": 1,
        "maxItems": 50
       }
      },
      "required": [
       "type",
       "items"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "quote"
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "cite": {
        "type": "string",
        "maxLength": 200
       },
       "source": {
        "type": "string",
        "format": "uri",
        "maxLength": 2048
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "callout"
       },
       "title": {
        "type": "string",
        "maxLength": 200
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "appCta"
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "href": {
        "$ref": "#/properties/sections/items/anyOf/3/properties/source"
       },
       "label": {
        "type": "string",
        "minLength": 1,
        "maxLength": 80
       }
      },
      "required": [
       "type",
       "text",
       "href",
       "label"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "comparisonTable"
       },
       "caption": {
        "type": "string",
        "maxLength": 300
       },
       "columns": {
        "type": "array",
        "items": {
         "type": "string",
         "maxLength": 200
        },
        "minItems": 2,
        "maxItems": 8
       },
       "rows": {
        "type": "array",
        "items": {
         "type": "array",
         "items": {
          "type": "string",
          "maxLength": 1000
         }
        },
        "minItems": 1,
        "maxItems": 50
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "columns",
       "rows"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "image"
       },
       "src": {
        "$ref": "#/properties/sections/items/anyOf/3/properties/source"
       },
       "alt": {
        "type": "string",
        "minLength": 1,
        "maxLength": 300
       },
       "caption": {
        "type": "string",
        "maxLength": 500
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "src",
       "alt"
      ],
      "additionalProperties": false
     }
    ]
   },
   "minItems": 1,
   "maxItems": 200
  },
  "faq": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "q": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300
     },
     "a": {
      "$ref": "#/properties/sections/items/anyOf/0/properties/text"
     }
    },
    "required": [
     "q",
     "a"
    ],
    "additionalProperties": false
   },
   "maxItems": 20
  },
  "seo": {
   "type": "object",
   "properties": {
    "answer": {
     "type": "string",
     "maxLength": 2000
    },
    "slug": {
     "type": "string",
     "maxLength": 200
    },
    "canonical": {
     "$ref": "#/properties/sections/items/anyOf/3/properties/source"
    },
    "keywords": {
     "type": "array",
     "items": {
      "type": "string",
      "maxLength": 80
     },
     "maxItems": 20
    }
   },
   "additionalProperties": false
  },
  "design_tokens": {
   "type": "object",
   "additionalProperties": {}
  }
 },
 "required": [
  "tenant_slug",
  "kind",
  "title",
  "description",
  "sections"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "preview_id": {
     "type": "string"
    },
    "url": {
     "type": "string"
    },
    "html": {
     "type": "string"
    },
    "expires_at": {
     "type": "string"
    }
   },
   "required": [
    "preview_id",
    "url",
    "html",
    "expires_at"
   ],
   "additionalProperties": false
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "type": "object",
     "additionalProperties": {}
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `content_lint` — read-only

**Lint content.** The MACHINE half of the blog-checker playbook, no side effects: answer-first ≤ 60 words, each H2 followed by a 40–75-word paragraph, table/list for comparison·listicle·how-to, FAQ 3–5 × 80–150 words, inline HTML allowlist (em/strong/a), ≥ 2 outbound citations, ≥ 2 numeric statistics, ≥ 1 original artifact, hub assigned, localised slug/title/description vs master, style rotation vs recent_styles, banned phrases, first-person density. Returns warnings [{code, message, path?, severity}] and stats {words, h2s, citations, numbers, faq, artifacts, urls}. severity "error" = playbook rule violated.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "kind": {
   "type": "string",
   "enum": [
    "article",
    "page"
   ]
  },
  "title": {
   "type": "string",
   "minLength": 1,
   "maxLength": 300
  },
  "description": {
   "type": "string",
   "minLength": 1,
   "maxLength": 1000
  },
  "sections": {
   "type": "array",
   "items": {
    "anyOf": [
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "paragraph"
       },
       "text": {
        "type": "string",
        "minLength": 1,
        "maxLength": 20000
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "heading"
       },
       "level": {
        "type": "number",
        "enum": [
         2,
         3
        ]
       },
       "text": {
        "type": "string",
        "minLength": 1,
        "maxLength": 300
       }
      },
      "required": [
       "type",
       "level",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "list"
       },
       "ordered": {
        "type": "boolean"
       },
       "items": {
        "type": "array",
        "items": {
         "$ref": "#/properties/sections/items/anyOf/0/properties/text"
        },
        "minItems": 1,
        "maxItems": 50
       }
      },
      "required": [
       "type",
       "items"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "quote"
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "cite": {
        "type": "string",
        "maxLength": 200
       },
       "source": {
        "type": "string",
        "format": "uri",
        "maxLength": 2048
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "callout"
       },
       "title": {
        "type": "string",
        "maxLength": 200
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "appCta"
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "href": {
        "$ref": "#/properties/sections/items/anyOf/3/properties/source"
       },
       "label": {
        "type": "string",
        "minLength": 1,
        "maxLength": 80
       }
      },
      "required": [
       "type",
       "text",
       "href",
       "label"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "comparisonTable"
       },
       "caption": {
        "type": "string",
        "maxLength": 300
       },
       "columns": {
        "type": "array",
        "items": {
         "type": "string",
         "maxLength": 200
        },
        "minItems": 2,
        "maxItems": 8
       },
       "rows": {
        "type": "array",
        "items": {
         "type": "array",
         "items": {
          "type": "string",
          "maxLength": 1000
         }
        },
        "minItems": 1,
        "maxItems": 50
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "columns",
       "rows"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "image"
       },
       "src": {
        "$ref": "#/properties/sections/items/anyOf/3/properties/source"
       },
       "alt": {
        "type": "string",
        "minLength": 1,
        "maxLength": 300
       },
       "caption": {
        "type": "string",
        "maxLength": 500
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "src",
       "alt"
      ],
      "additionalProperties": false
     }
    ]
   },
   "minItems": 1,
   "maxItems": 200
  },
  "faq": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "q": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300
     },
     "a": {
      "$ref": "#/properties/sections/items/anyOf/0/properties/text"
     }
    },
    "required": [
     "q",
     "a"
    ],
    "additionalProperties": false
   },
   "maxItems": 20
  },
  "seo": {
   "type": "object",
   "properties": {
    "answer": {
     "type": "string",
     "maxLength": 2000
    },
    "slug": {
     "type": "string",
     "maxLength": 200
    },
    "canonical": {
     "$ref": "#/properties/sections/items/anyOf/3/properties/source"
    },
    "keywords": {
     "type": "array",
     "items": {
      "type": "string",
      "maxLength": 80
     },
     "maxItems": 20
    }
   },
   "additionalProperties": false
  },
  "design_tokens": {
   "type": "object",
   "additionalProperties": {}
  },
  "style": {
   "type": "string",
   "enum": [
    "guide",
    "how-to",
    "comparison",
    "listicle",
    "explainer",
    "opinion",
    "news",
    "case-study"
   ]
  },
  "hub": {
   "type": "string",
   "maxLength": 100
  },
  "locale": {
   "type": "string",
   "maxLength": 16
  },
  "master": {
   "type": "object",
   "properties": {
    "slug": {
     "type": "string",
     "maxLength": 200
    },
    "title": {
     "type": "string",
     "maxLength": 300
    },
    "description": {
     "type": "string",
     "maxLength": 1000
    }
   },
   "additionalProperties": false
  },
  "recent_styles": {
   "type": "array",
   "items": {
    "type": "string",
    "maxLength": 40
   },
   "maxItems": 5
  },
  "brand": {
   "type": "object",
   "properties": {
    "banned_phrases": {
     "type": "array",
     "items": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120
     },
     "maxItems": 100
    },
    "own_hosts": {
     "type": "array",
     "items": {
      "type": "string",
      "maxLength": 253
     },
     "maxItems": 10
    }
   },
   "additionalProperties": false
  }
 },
 "required": [
  "kind",
  "title",
  "description",
  "sections"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "warnings": {
     "type": "array",
     "items": {
      "type": "object",
      "properties": {
       "code": {
        "type": "string"
       },
       "message": {
        "type": "string"
       },
       "path": {
        "type": "string"
       },
       "severity": {
        "type": "string",
        "enum": [
         "error",
         "warn"
        ]
       }
      },
      "required": [
       "code",
       "message",
       "severity"
      ],
      "additionalProperties": false
     }
    },
    "stats": {
     "type": "object",
     "properties": {
      "words": {
       "type": "number"
      },
      "h2s": {
       "type": "number"
      },
      "citations": {
       "type": "number"
      },
      "numbers": {
       "type": "number"
      }
     },
     "required": [
      "words",
      "h2s",
      "citations",
      "numbers"
     ],
     "additionalProperties": true
    }
   },
   "required": [
    "warnings",
    "stats"
   ],
   "additionalProperties": false
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "type": "object",
     "additionalProperties": {}
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `check_record`

**Record checker verdict.** Append one blog-checker verdict for a draft: tenant_slug, subject_kind (article | page | instagram), subject_ref (the Context issue id, or a draft id before one exists), round (1 = first check), verdict (pass | bounce | escalate), findings (the verdict document's checks, as JSON) and models ({maker, checker}). Returns check_id. Approvals still live in Context — this is the audit trail on the publishing side. Caller must be a member of tenant_slug.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "subject_kind": {
   "type": "string",
   "enum": [
    "article",
    "page",
    "instagram"
   ]
  },
  "subject_ref": {
   "type": "string",
   "minLength": 1,
   "maxLength": 200
  },
  "round": {
   "type": "integer",
   "minimum": 1,
   "maximum": 99
  },
  "verdict": {
   "type": "string",
   "enum": [
    "pass",
    "bounce",
    "escalate"
   ]
  },
  "findings": {
   "type": "array",
   "items": {
    "type": "object",
    "additionalProperties": {}
   },
   "maxItems": 200
  },
  "models": {
   "type": "object",
   "properties": {
    "maker": {
     "type": "string",
     "maxLength": 120
    },
    "checker": {
     "type": "string",
     "maxLength": 120
    }
   },
   "additionalProperties": true
  }
 },
 "required": [
  "tenant_slug",
  "subject_kind",
  "subject_ref",
  "round",
  "verdict",
  "findings",
  "models"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "check_id": {
     "type": "string"
    }
   },
   "required": [
    "check_id"
   ],
   "additionalProperties": false
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "type": "object",
     "additionalProperties": {}
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `check_list` — read-only

**List checker verdicts.** Every recorded blog-checker verdict for a subject_ref (Context issue id or draft id), oldest round first — use it to know the current round before bouncing again (bounce ≤ 2 rounds, then escalate). Only rows for tenants the caller is a member of are returned.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "subject_ref": {
   "type": "string",
   "minLength": 1,
   "maxLength": 200
  }
 },
 "required": [
  "subject_ref"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "properties": {
    "checks": {
     "type": "array",
     "items": {
      "type": "object",
      "properties": {
       "check_id": {
        "type": "string"
       },
       "tenant_slug": {
        "type": "string"
       },
       "subject_kind": {
        "type": "string"
       },
       "subject_ref": {
        "type": "string"
       },
       "round": {
        "type": "number"
       },
       "verdict": {
        "type": "string"
       },
       "findings": {},
       "models": {},
       "created_at": {
        "type": "string"
       }
      },
      "required": [
       "check_id",
       "tenant_slug",
       "subject_kind",
       "subject_ref",
       "round",
       "verdict",
       "created_at"
      ],
      "additionalProperties": false
     }
    }
   },
   "required": [
    "checks"
   ],
   "additionalProperties": false
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "type": "object",
     "additionalProperties": {}
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

## Tenant & brand (8 · all)

### `tenant_create`

**Create tenant.** Create a tenant (a site such as getmeetly.ai/blog). The creator becomes its owner member (blog.tenant_members); membership already granted for the slug is honoured. tier: artifact (drafts only) | review (checker gate) | hosted (renderer + domains + IndexNow on publish; analytics ids via tenant_update { tracking }, see tracking_setup_status). Returns the tenant.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "name": {
   "type": "string",
   "minLength": 1,
   "maxLength": 120
  },
  "tier": {
   "type": "string",
   "enum": [
    "artifact",
    "review",
    "hosted"
   ]
  },
  "products": {
   "type": "array",
   "items": {
    "type": "string",
    "enum": [
     "blog",
     "site",
     "instagram"
    ]
   },
   "minItems": 1,
   "maxItems": 3
  },
  "recurrence": {
   "type": "object",
   "additionalProperties": {}
  },
  "locales": {
   "type": "array",
   "items": {
    "type": "string",
    "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
   },
   "minItems": 1,
   "maxItems": 20
  },
  "default_locale": {
   "$ref": "#/properties/locales/items"
  },
  "timezone": {
   "type": "string",
   "minLength": 1,
   "maxLength": 64
  },
  "mount_path": {
   "type": "string",
   "pattern": "^\\/[a-z0-9/-]*$",
   "maxLength": 120
  },
  "primary_hostname": {
   "type": "string",
   "pattern": "^(?=.{1,253}$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$"
  },
  "context_epic_id": {
   "type": "string",
   "format": "uuid"
  }
 },
 "required": [
  "slug",
  "name",
  "tier"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `tenant_get` — read-only

**Get tenant.** Tenant settings plus brand profile, audiences, hubs, domains and content counts. Caller must be a member.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  }
 },
 "required": [
  "slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `tenant_update`

**Update tenant.** Patch tenant settings (name, tier, products, recurrence, locales, default_locale, timezone, mount_path, primary_hostname, context_epic_id, context_epic_surface: phone | web — where the epic was created, so the settings page leads with context://tasks/<epic> or the web URL). Omitted fields keep their value; pass null to clear primary_hostname / context_epic_id. `tracking` (E8) patches blog.tracking_config — the one source of truth for analytics: umami_website_id (the renderer injects the Umami script when UMAMI_SCRIPT_URL is set on the host), ga4_measurement_id (BYO, consent-gated), gsc_property, bing_site_url, indexnow_enabled, brand_aliases / owned_domains (probe detection), probe_markets. Ids only — API keys stay on the host by NAME.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "name": {
   "type": "string",
   "minLength": 1,
   "maxLength": 120
  },
  "tier": {
   "type": "string",
   "enum": [
    "artifact",
    "review",
    "hosted"
   ]
  },
  "products": {
   "type": "array",
   "items": {
    "type": "string",
    "enum": [
     "blog",
     "site",
     "instagram"
    ]
   },
   "minItems": 1,
   "maxItems": 3
  },
  "recurrence": {
   "type": "object",
   "additionalProperties": {}
  },
  "locales": {
   "type": "array",
   "items": {
    "type": "string",
    "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
   },
   "minItems": 1,
   "maxItems": 20
  },
  "default_locale": {
   "$ref": "#/properties/locales/items"
  },
  "timezone": {
   "type": "string",
   "minLength": 1,
   "maxLength": 64
  },
  "mount_path": {
   "type": "string",
   "pattern": "^\\/[a-z0-9/-]*$",
   "maxLength": 120
  },
  "primary_hostname": {
   "anyOf": [
    {
     "type": "string",
     "pattern": "^(?=.{1,253}$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$"
    },
    {
     "type": "null"
    }
   ]
  },
  "context_epic_id": {
   "anyOf": [
    {
     "type": "string",
     "format": "uuid"
    },
    {
     "type": "null"
    }
   ]
  },
  "context_epic_surface": {
   "type": "string",
   "enum": [
    "phone",
    "web"
   ]
  },
  "tracking": {
   "type": "object",
   "properties": {
    "umami_website_id": {
     "anyOf": [
      {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{4,64}$"
      },
      {
       "type": "null"
      }
     ]
    },
    "ga4_measurement_id": {
     "anyOf": [
      {
       "type": "string",
       "pattern": "^G-[A-Z0-9]{4,16}$"
      },
      {
       "type": "null"
      }
     ]
    },
    "gsc_property": {
     "anyOf": [
      {
       "type": "string",
       "maxLength": 300
      },
      {
       "type": "null"
      }
     ]
    },
    "bing_site_url": {
     "anyOf": [
      {
       "type": "string",
       "format": "uri",
       "maxLength": 300
      },
      {
       "type": "null"
      }
     ]
    },
    "indexnow_enabled": {
     "type": "boolean"
    },
    "brand_aliases": {
     "type": "array",
     "items": {
      "type": "string",
      "minLength": 1,
      "maxLength": 80
     },
     "maxItems": 30
    },
    "owned_domains": {
     "type": "array",
     "items": {
      "$ref": "#/properties/primary_hostname/anyOf/0"
     },
     "maxItems": 30
    },
    "probe_markets": {
     "type": "array",
     "items": {
      "type": "string",
      "pattern": "^[a-z]{2}$"
     },
     "minItems": 1,
     "maxItems": 10
    }
   },
   "additionalProperties": false
  }
 },
 "required": [
  "slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `brand_upsert`

**Upsert brand profile.** Replace the tenant's brand profile: voice (persona, banned_phrases[], …), byline (author name/url), design_tokens (blog-agent/templates/design-tokens.json shape — used by previews and the renderer), optional organization_ld / software_application_ld (JSON-LD). Bumps revision and keeps the previous state in blog.brand_profile_revisions.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "voice": {
   "type": "object",
   "additionalProperties": {}
  },
  "byline": {
   "$ref": "#/properties/voice"
  },
  "design_tokens": {
   "type": "object",
   "additionalProperties": {}
  },
  "organization_ld": {
   "$ref": "#/properties/voice"
  },
  "software_application_ld": {
   "$ref": "#/properties/voice"
  }
 },
 "required": [
  "tenant_slug",
  "voice",
  "byline",
  "design_tokens"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `audiences_upsert`

**Upsert audiences.** Upsert audience segments by key (name, description, pains[], queries[]). Existing keys are replaced; others untouched.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "items": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "key": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
     },
     "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120
     },
     "description": {
      "type": "string",
      "maxLength": 2000
     },
     "pains": {
      "type": "array",
      "items": {
       "type": "string",
       "maxLength": 300
      },
      "maxItems": 50
     },
     "queries": {
      "type": "array",
      "items": {
       "type": "string",
       "maxLength": 300
      },
      "maxItems": 100
     }
    },
    "required": [
     "key",
     "name"
    ],
    "additionalProperties": false
   },
   "minItems": 1,
   "maxItems": 50
  }
 },
 "required": [
  "tenant_slug",
  "items"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `hubs_upsert`

**Upsert hubs.** Upsert topic hubs by slug (title, description, pillar_article_id). Every article must belong to a hub (the lint's hub_missing rule).

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "items": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "slug": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
     },
     "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
     },
     "description": {
      "type": "string",
      "maxLength": 2000
     },
     "pillar_article_id": {
      "type": "string",
      "format": "uuid"
     }
    },
    "required": [
     "slug",
     "title"
    ],
    "additionalProperties": false
   },
   "minItems": 1,
   "maxItems": 50
  }
 },
 "required": [
  "tenant_slug",
  "items"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `topics_upsert`

**Upsert topics.** Create or update topics (the research backlog): hub_slug, title, target_query, fanout_queries[], status (idea | researched | drafting | published | retired), context_issue_id, source. Pass id to update.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "items": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "format": "uuid"
     },
     "hub_slug": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
     },
     "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300
     },
     "target_query": {
      "type": "string",
      "maxLength": 300
     },
     "fanout_queries": {
      "type": "array",
      "items": {
       "type": "string",
       "maxLength": 300
      },
      "maxItems": 50
     },
     "status": {
      "type": "string",
      "enum": [
       "idea",
       "researched",
       "drafting",
       "published",
       "retired"
      ]
     },
     "context_issue_id": {
      "$ref": "#/properties/items/items/properties/id"
     },
     "source": {
      "type": "string",
      "maxLength": 200
     }
    },
    "required": [
     "title"
    ],
    "additionalProperties": false
   },
   "minItems": 1,
   "maxItems": 100
  }
 },
 "required": [
  "tenant_slug",
  "items"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `topics_list` — read-only

**List topics.** Topics of a tenant, newest first, optionally filtered by status.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "status": {
   "type": "string",
   "enum": [
    "idea",
    "researched",
    "drafting",
    "published",
    "retired"
   ]
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

## Content (articles/pages) (9 · all)

### `article_upsert`

**Upsert article.** Create or update an article draft. Same body as content_lint / preview_render. Runs the playbook lint: severity-error findings REJECT the call (invalid_argument, details.warnings) unless dry_run; warnings are always returned. Unique per (tenant, locale, slug); pass id to update (status is kept). translation_group ties locale variants together (the default-locale row is the master used for the localisation check). context_issue_id is the Context issue's uuid. Returns { id, status, warnings[], preview_url, url }.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  },
  "slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,199}$"
  },
  "locale": {
   "type": "string",
   "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
  },
  "translation_group": {
   "type": "string",
   "minLength": 1,
   "maxLength": 200
  },
  "title": {
   "type": "string",
   "minLength": 1,
   "maxLength": 300
  },
  "description": {
   "type": "string",
   "minLength": 1,
   "maxLength": 1000
  },
  "sections": {
   "type": "array",
   "items": {
    "anyOf": [
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "paragraph"
       },
       "text": {
        "type": "string",
        "minLength": 1,
        "maxLength": 20000
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "heading"
       },
       "level": {
        "type": "number",
        "enum": [
         2,
         3
        ]
       },
       "text": {
        "type": "string",
        "minLength": 1,
        "maxLength": 300
       }
      },
      "required": [
       "type",
       "level",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "list"
       },
       "ordered": {
        "type": "boolean"
       },
       "items": {
        "type": "array",
        "items": {
         "$ref": "#/properties/sections/items/anyOf/0/properties/text"
        },
        "minItems": 1,
        "maxItems": 50
       }
      },
      "required": [
       "type",
       "items"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "quote"
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "cite": {
        "type": "string",
        "maxLength": 200
       },
       "source": {
        "type": "string",
        "format": "uri",
        "maxLength": 2048
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "callout"
       },
       "title": {
        "type": "string",
        "maxLength": 200
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "appCta"
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "href": {
        "$ref": "#/properties/sections/items/anyOf/3/properties/source"
       },
       "label": {
        "type": "string",
        "minLength": 1,
        "maxLength": 80
       }
      },
      "required": [
       "type",
       "text",
       "href",
       "label"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "comparisonTable"
       },
       "caption": {
        "type": "string",
        "maxLength": 300
       },
       "columns": {
        "type": "array",
        "items": {
         "type": "string",
         "maxLength": 200
        },
        "minItems": 2,
        "maxItems": 8
       },
       "rows": {
        "type": "array",
        "items": {
         "type": "array",
         "items": {
          "type": "string",
          "maxLength": 1000
         }
        },
        "minItems": 1,
        "maxItems": 50
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "columns",
       "rows"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "image"
       },
       "src": {
        "$ref": "#/properties/sections/items/anyOf/3/properties/source"
       },
       "alt": {
        "type": "string",
        "minLength": 1,
        "maxLength": 300
       },
       "caption": {
        "type": "string",
        "maxLength": 500
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "src",
       "alt"
      ],
      "additionalProperties": false
     }
    ]
   },
   "minItems": 1,
   "maxItems": 200
  },
  "faq": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "q": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300
     },
     "a": {
      "$ref": "#/properties/sections/items/anyOf/0/properties/text"
     }
    },
    "required": [
     "q",
     "a"
    ],
    "additionalProperties": false
   },
   "maxItems": 20
  },
  "seo": {
   "type": "object",
   "properties": {
    "answer": {
     "type": "string",
     "maxLength": 2000
    },
    "slug": {
     "type": "string",
     "maxLength": 200
    },
    "canonical": {
     "$ref": "#/properties/sections/items/anyOf/3/properties/source"
    },
    "keywords": {
     "type": "array",
     "items": {
      "type": "string",
      "maxLength": 80
     },
     "maxItems": 20
    }
   },
   "additionalProperties": false
  },
  "style": {
   "type": "string",
   "enum": [
    "guide",
    "how-to",
    "comparison",
    "listicle",
    "explainer",
    "opinion",
    "news",
    "case-study"
   ]
  },
  "hub_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "topic_id": {
   "$ref": "#/properties/id"
  },
  "hero_asset_id": {
   "$ref": "#/properties/id"
  },
  "faq_title": {
   "type": "string",
   "maxLength": 200
  },
  "tags": {
   "type": "array",
   "items": {
    "type": "string",
    "minLength": 1,
    "maxLength": 60
   },
   "maxItems": 20
  },
  "category": {
   "type": "string",
   "maxLength": 80
  },
  "internal_links": {
   "type": "array",
   "items": {
    "type": "string",
    "maxLength": 300
   },
   "maxItems": 50
  },
  "publish_at": {
   "type": "string",
   "format": "date-time"
  },
  "context_issue_id": {
   "$ref": "#/properties/id"
  },
  "dry_run": {
   "type": "boolean"
  }
 },
 "required": [
  "tenant_slug",
  "slug",
  "locale",
  "translation_group",
  "title",
  "description",
  "sections",
  "seo",
  "hub_slug",
  "context_issue_id"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `article_get` — read-only

**Get article.** One article by id, or by slug + locale. Full body.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  },
  "slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,199}$"
  },
  "locale": {
   "type": "string",
   "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `article_list` — read-only

**List articles.** Articles of a tenant, newest first, without bodies. Filters: status, locale, hub_slug. Pagination: limit (default 20, max 100) and the opaque cursor from next_cursor.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "status": {
   "type": "string",
   "enum": [
    "draft",
    "in_review",
    "approved",
    "published",
    "retired"
   ]
  },
  "locale": {
   "type": "string",
   "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
  },
  "hub_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "limit": {
   "type": "integer",
   "minimum": 1,
   "maximum": 100
  },
  "cursor": {
   "type": "string",
   "maxLength": 400
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `article_set_status`

**Set article status.** Move an article one step forward: draft → in_review → approved → published → retired (any other jump is `conflict`). Reaching published requires the article's context_issue_id AND assert_context_done: true — your assertion that the Context issue carries the owner's approval; it is recorded on blog.publish_events. Prefer `publish` for the final step.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  },
  "status": {
   "type": "string",
   "enum": [
    "draft",
    "in_review",
    "approved",
    "published",
    "retired"
   ]
  },
  "assert_context_done": {
   "type": "boolean"
  }
 },
 "required": [
  "tenant_slug",
  "id",
  "status"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `page_upsert`

**Upsert page.** Create or update a site page (about, pricing, landing). Same body as content_lint with kind page: the article-only rules (answer-first, FAQ shape, hub, citations, statistics, artifact) do not apply; HTML allowlist and banned phrases do. Severity-error findings reject unless dry_run. Unique per (tenant, locale, slug). Returns { id, status, warnings[], preview_url, url }.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  },
  "slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,199}$"
  },
  "locale": {
   "type": "string",
   "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
  },
  "title": {
   "type": "string",
   "minLength": 1,
   "maxLength": 300
  },
  "description": {
   "type": "string",
   "minLength": 1,
   "maxLength": 1000
  },
  "sections": {
   "type": "array",
   "items": {
    "anyOf": [
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "paragraph"
       },
       "text": {
        "type": "string",
        "minLength": 1,
        "maxLength": 20000
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "heading"
       },
       "level": {
        "type": "number",
        "enum": [
         2,
         3
        ]
       },
       "text": {
        "type": "string",
        "minLength": 1,
        "maxLength": 300
       }
      },
      "required": [
       "type",
       "level",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "list"
       },
       "ordered": {
        "type": "boolean"
       },
       "items": {
        "type": "array",
        "items": {
         "$ref": "#/properties/sections/items/anyOf/0/properties/text"
        },
        "minItems": 1,
        "maxItems": 50
       }
      },
      "required": [
       "type",
       "items"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "quote"
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "cite": {
        "type": "string",
        "maxLength": 200
       },
       "source": {
        "type": "string",
        "format": "uri",
        "maxLength": 2048
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "callout"
       },
       "title": {
        "type": "string",
        "maxLength": 200
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "text"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "appCta"
       },
       "text": {
        "$ref": "#/properties/sections/items/anyOf/0/properties/text"
       },
       "href": {
        "$ref": "#/properties/sections/items/anyOf/3/properties/source"
       },
       "label": {
        "type": "string",
        "minLength": 1,
        "maxLength": 80
       }
      },
      "required": [
       "type",
       "text",
       "href",
       "label"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "comparisonTable"
       },
       "caption": {
        "type": "string",
        "maxLength": 300
       },
       "columns": {
        "type": "array",
        "items": {
         "type": "string",
         "maxLength": 200
        },
        "minItems": 2,
        "maxItems": 8
       },
       "rows": {
        "type": "array",
        "items": {
         "type": "array",
         "items": {
          "type": "string",
          "maxLength": 1000
         }
        },
        "minItems": 1,
        "maxItems": 50
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "columns",
       "rows"
      ],
      "additionalProperties": false
     },
     {
      "type": "object",
      "properties": {
       "type": {
        "type": "string",
        "const": "image"
       },
       "src": {
        "$ref": "#/properties/sections/items/anyOf/3/properties/source"
       },
       "alt": {
        "type": "string",
        "minLength": 1,
        "maxLength": 300
       },
       "caption": {
        "type": "string",
        "maxLength": 500
       },
       "original": {
        "type": "boolean"
       }
      },
      "required": [
       "type",
       "src",
       "alt"
      ],
      "additionalProperties": false
     }
    ]
   },
   "minItems": 1,
   "maxItems": 200
  },
  "faq": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "q": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300
     },
     "a": {
      "$ref": "#/properties/sections/items/anyOf/0/properties/text"
     }
    },
    "required": [
     "q",
     "a"
    ],
    "additionalProperties": false
   },
   "maxItems": 20
  },
  "seo": {
   "type": "object",
   "properties": {
    "answer": {
     "type": "string",
     "maxLength": 2000
    },
    "slug": {
     "type": "string",
     "maxLength": 200
    },
    "canonical": {
     "$ref": "#/properties/sections/items/anyOf/3/properties/source"
    },
    "keywords": {
     "type": "array",
     "items": {
      "type": "string",
      "maxLength": 80
     },
     "maxItems": 20
    }
   },
   "additionalProperties": false
  },
  "design_overrides": {
   "type": "object",
   "additionalProperties": {}
  },
  "context_issue_id": {
   "$ref": "#/properties/id"
  },
  "dry_run": {
   "type": "boolean"
  }
 },
 "required": [
  "tenant_slug",
  "slug",
  "locale",
  "title",
  "description",
  "sections",
  "seo",
  "context_issue_id"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `page_get` — read-only

**Get page.** One page by id, or by slug + locale. Full body.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  },
  "slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,199}$"
  },
  "locale": {
   "type": "string",
   "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `page_list` — read-only

**List pages.** Pages of a tenant, newest first, without bodies. Filters: status, locale. Paginated like article_list.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "status": {
   "type": "string",
   "enum": [
    "draft",
    "in_review",
    "approved",
    "published",
    "retired"
   ]
  },
  "locale": {
   "type": "string",
   "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
  },
  "limit": {
   "type": "integer",
   "minimum": 1,
   "maximum": 100
  },
  "cursor": {
   "type": "string",
   "maxLength": 400
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `page_set_status`

**Set page status.** Move a page one step forward (draft → in_review → approved → published → retired). published requires context_issue_id on the page and assert_context_done: true, recorded on blog.publish_events.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  },
  "status": {
   "type": "string",
   "enum": [
    "draft",
    "in_review",
    "approved",
    "published",
    "retired"
   ]
  },
  "assert_context_done": {
   "type": "boolean"
  }
 },
 "required": [
  "tenant_slug",
  "id",
  "status"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `publish`

**Publish.** Publish exactly one of article_id | page_id | instagram_post_id. Requires the row to be approved (→ action publish) or already published (→ refresh), context_issue_id (must match the row's when set) and assert_context_done: true — your assertion that the Context issue carries the owner's approval, recorded as asserted_done_by on blog.publish_events. Sets published + published_at, purges the renderer's cache, pings IndexNow for hosted tenants with a primary_hostname (INDEXNOW_KEY on the host; the outcome is in `indexnow` and never fails the publish) and returns { url, published_at, action, publish_event_id, indexnow }.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "article_id": {
   "type": "string",
   "format": "uuid"
  },
  "page_id": {
   "$ref": "#/properties/article_id"
  },
  "instagram_post_id": {
   "$ref": "#/properties/article_id"
  },
  "assert_context_done": {
   "type": "boolean"
  },
  "context_issue_id": {
   "$ref": "#/properties/article_id"
  }
 },
 "required": [
  "tenant_slug",
  "assert_context_done",
  "context_issue_id"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

## Assets (2 · all)

### `asset_upload`

**Request asset upload.** Reserve an asset row and get a signed PUT URL into the Supabase Storage bucket blog-assets (valid 2 h). content_type ∈ image/jpeg | image/png | image/webp | image/gif | image/avif | image/svg+xml | video/mp4; size ≤ 26214400 bytes; sha256 of the file. PUT the bytes to upload.url with upload.headers, then call asset_complete. Returns { asset_id, storage_path, public_url, upload }.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "filename": {
   "type": "string",
   "minLength": 1,
   "maxLength": 200
  },
  "size": {
   "type": "integer",
   "minimum": 1,
   "maximum": 26214400
  },
  "sha256": {
   "type": "string",
   "pattern": "^[a-f0-9]{64}$"
  },
  "content_type": {
   "type": "string",
   "enum": [
    "image/jpeg",
    "image/png",
    "image/webp",
    "image/gif",
    "image/avif",
    "image/svg+xml",
    "video/mp4"
   ]
  },
  "kind": {
   "type": "string",
   "enum": [
    "image",
    "video",
    "file"
   ]
  }
 },
 "required": [
  "tenant_slug",
  "filename",
  "size",
  "sha256",
  "content_type"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `asset_complete`

**Complete asset upload.** Mark an uploaded asset ready and record width / height / alt. Returns the asset with its public_url — use asset_id as hero_asset_id or an Instagram slide.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "asset_id": {
   "type": "string",
   "format": "uuid"
  },
  "width": {
   "type": "integer",
   "minimum": 1,
   "maximum": 20000
  },
  "height": {
   "type": "integer",
   "minimum": 1,
   "maximum": 20000
  },
  "alt": {
   "type": "string",
   "maxLength": 300
  }
 },
 "required": [
  "tenant_slug",
  "asset_id"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

## Instagram (4 · all)

### `instagram_post_upsert`

**Upsert Instagram post.** Create or update an Instagram carousel draft: caption (≤ 2200 chars), slides (asset ids of this tenant, 1–10, in order), publish_at, context_issue_id. Pass id to update (status kept). Scheduling through Postiz is a later slice; publish marks it published and records the event.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  },
  "caption": {
   "type": "string",
   "minLength": 1,
   "maxLength": 2200
  },
  "slides": {
   "type": "array",
   "items": {
    "$ref": "#/properties/id"
   },
   "minItems": 1,
   "maxItems": 10
  },
  "publish_at": {
   "type": "string",
   "format": "date-time"
  },
  "context_issue_id": {
   "$ref": "#/properties/id"
  }
 },
 "required": [
  "tenant_slug",
  "caption",
  "slides",
  "context_issue_id"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `instagram_post_get` — read-only

**Get Instagram post.** One Instagram post by id: caption, slides, status, context_issue_id, Postiz id and permalink once posted.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  }
 },
 "required": [
  "tenant_slug",
  "id"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `instagram_post_list` — read-only

**List Instagram posts.** Instagram posts of a tenant, newest first. Filter: status. Paginated like article_list.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "status": {
   "type": "string",
   "enum": [
    "draft",
    "in_review",
    "approved",
    "published",
    "retired"
   ]
  },
  "limit": {
   "type": "integer",
   "minimum": 1,
   "maximum": 100
  },
  "cursor": {
   "type": "string",
   "maxLength": 400
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `instagram_post_set_status`

**Set Instagram post status.** Move an Instagram post one step forward: draft → in_review → approved → published → retired (any other jump is `conflict`). Reaching published requires the post's context_issue_id AND assert_context_done: true — recorded on blog.publish_events with channel instagram. Prefer `publish` for the final step; it needs approved first, and this is how a draft gets there.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "id": {
   "type": "string",
   "format": "uuid"
  },
  "status": {
   "type": "string",
   "enum": [
    "draft",
    "in_review",
    "approved",
    "published",
    "retired"
   ]
  },
  "assert_context_done": {
   "type": "boolean"
  }
 },
 "required": [
  "tenant_slug",
  "id",
  "status"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

## Domain (2 · all)

### `domain_connect`

**Connect domain.** Record a hostname for the tenant and return the DNS / proxy instructions plus copy-paste snippets (vercel.json rewrites as getmeetly.ai uses them, next.config.js rewrites(), nginx location; the DNS record for cname). The same form lives on the tenant's Domain card at /tenants/<slug>. mode: cname (DNS → Vercel), proxy (the tenant's existing site reverse-proxies mount_path to the platform), platform (<slug>.<platform host>). With VERCEL_DOMAINS_ENABLED=true the hostname is also attached to the Vercel project (default off; the operator does it in the dashboard). Nothing is verified here — call domain_status.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "hostname": {
   "type": "string",
   "pattern": "^(?=.{1,253}$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$"
  },
  "mode": {
   "type": "string",
   "enum": [
    "proxy",
    "cname",
    "platform"
   ]
  }
 },
 "required": [
  "tenant_slug",
  "hostname",
  "mode"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `domain_status`

**Domain status.** Verify a connected hostname and store the result. proxy: GET https://<hostname><mount_path>/_health (5 s) — verified when the platform's JSON says tenant = this slug; the probe ({ ok, url, http_status, tenant, error, checked_at }) is returned and kept as probe_status. cname / platform: the Vercel Domains API decides when VERCEL_DOMAINS_ENABLED=true, otherwise the same probe. Same check as the Verify button on /tenants/<slug>.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "hostname": {
   "type": "string",
   "pattern": "^(?=.{1,253}$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$"
  }
 },
 "required": [
  "tenant_slug",
  "hostname"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

## Analytics (10 · all)

### `stats_summary` — read-only

**Stats summary.** How the blog is doing over a period (7d | 28d | 90d, default 28d, ending yesterday; or from/to): totals (pageviews, visitors, visits, bounces, time), the previous period for comparison, channel split with the AI-referral share and per-engine visitors, Search Console clicks/impressions/position when ingested, AI-visibility cited rate when a probe ran, top 5 posts. Every source that is not wired is listed in not_configured by NAME — numbers are never invented.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "period": {
   "type": "string",
   "enum": [
    "7d",
    "28d",
    "90d"
   ]
  },
  "from": {
   "type": "string",
   "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
  },
  "to": {
   "$ref": "#/properties/from"
  },
  "compare_previous": {
   "type": "boolean"
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `top_posts` — read-only

**Top posts.** Posts ranked by pageviews over the period (default 28d), joined to the article (title, url, locale) with AI-referral pageviews and Search Console clicks per post. Paths that are not a published article (hub, old URLs) are listed with article_id null.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "period": {
   "type": "string",
   "enum": [
    "7d",
    "28d",
    "90d"
   ]
  },
  "from": {
   "type": "string",
   "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
  },
  "to": {
   "$ref": "#/properties/from"
  },
  "limit": {
   "type": "integer",
   "minimum": 1,
   "maximum": 50
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `post_stats` — read-only

**Post stats.** One article over the period (by article_id, or slug + locale): daily pageviews, AI-referral pageviews per engine, Search Console totals + top queries for its URL, and the probe results that cited it. Sources not ingested come back null / empty, never estimated.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "article_id": {
   "type": "string",
   "format": "uuid"
  },
  "slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,199}$"
  },
  "locale": {
   "type": "string",
   "pattern": "^[a-z]{2}(-[A-Za-z]{2})?$"
  },
  "period": {
   "type": "string",
   "enum": [
    "7d",
    "28d",
    "90d"
   ]
  },
  "from": {
   "type": "string",
   "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
  },
  "to": {
   "$ref": "#/properties/from"
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `ai_referrals` — read-only

**AI referrals.** Visitors and pageviews that arrived from AI assistants (chatgpt, perplexity, gemini, claude, copilot, grok, meta, you, google_aio) over the period: per engine, per day, and the landing pages. Referrer-based, so an under-count by design — pair with visibility_report.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "period": {
   "type": "string",
   "enum": [
    "7d",
    "28d",
    "90d"
   ]
  },
  "from": {
   "type": "string",
   "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
  },
  "to": {
   "$ref": "#/properties/from"
  },
  "engine": {
   "type": "string",
   "enum": [
    "chatgpt",
    "perplexity",
    "gemini",
    "claude",
    "copilot",
    "grok",
    "meta",
    "you",
    "google_aio"
   ]
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `search_queries` — read-only

**Search queries.** Search Console query × page rows over the period (ingested nightly when GSC_SERVICE_ACCOUNT_JSON and tracking.gsc_property are set): clicks, impressions, CTR, position, striking_distance (position 5–20 — a refresh candidate). Filters: min_impressions, page_url, striking_distance_only. Empty with a reason when Search Console is not wired.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "period": {
   "type": "string",
   "enum": [
    "7d",
    "28d",
    "90d"
   ]
  },
  "from": {
   "type": "string",
   "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
  },
  "to": {
   "$ref": "#/properties/from"
  },
  "source": {
   "type": "string",
   "enum": [
    "google",
    "bing"
   ]
  },
  "min_impressions": {
   "type": "integer",
   "minimum": 0,
   "maximum": 1000000
  },
  "page_url": {
   "type": "string",
   "format": "uri",
   "maxLength": 500
  },
  "striking_distance_only": {
   "type": "boolean"
  },
  "limit": {
   "type": "integer",
   "minimum": 1,
   "maximum": 200
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `visibility_report` — read-only

**AI-visibility report.** Per query × engine: the latest probe status in the period (cited | mentioned | absent | error | no_ai_answer), citation rank and URL, and the status in the previous period (trend). Plus cited rate per engine, overall, share of voice (most-cited domains, owned flagged) and the recent runs. Filters: engine, topic. Empty with a note when no run was ingested — the probe is agent-side (blog-assessment skill).

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "period": {
   "type": "string",
   "enum": [
    "7d",
    "28d",
    "90d"
   ]
  },
  "from": {
   "type": "string",
   "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
  },
  "to": {
   "$ref": "#/properties/from"
  },
  "engine": {
   "type": "string",
   "maxLength": 40
  },
  "topic": {
   "type": "string",
   "maxLength": 120
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `visibility_queries_set`

**Set visibility queries.** Upsert the tenant's target queries for the AI-visibility probe (≈ 30; the blog-assessment skill probes the active ones weekly): query, topic, priority (1 = money query probed daily, 3 = weekly), article_id (the post that should be cited), active. replace: true deactivates every query not in items. Returns the full active list.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "items": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "query": {
      "type": "string",
      "minLength": 3,
      "maxLength": 300
     },
     "topic": {
      "type": "string",
      "maxLength": 120
     },
     "priority": {
      "type": "integer",
      "minimum": 1,
      "maximum": 5
     },
     "article_id": {
      "type": "string",
      "format": "uuid"
     },
     "active": {
      "type": "boolean"
     }
    },
    "required": [
     "query"
    ],
    "additionalProperties": false
   },
   "minItems": 1,
   "maxItems": 200
  },
  "replace": {
   "type": "boolean"
  }
 },
 "required": [
  "tenant_slug",
  "items"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `visibility_results_ingest`

**Ingest visibility probe results.** Store one agent-side probe run (the blog-assessment skill: ~30 queries × engines, the owner's OPENROUTER_API_KEY etc. stay on the host). Pass run { trigger, engines, cost_usd, started_at?, finished_at?, status?, notes? } and results [{ query, engine, model_slug?, market?, sample_no?, status?, answer_text?, citations [{ url, title?, rank? }], cost_usd?, raw_response?, probed_at? }]. The server decides cited (an owned domain among the citations: tracking.owned_domains + primary_hostname + connected domains) / mentioned (a brand alias in the answer) / absent unless status is error | no_ai_answer, maps cited URLs to articles, and creates unknown queries as active. Returns run_id and counts.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "run": {
   "type": "object",
   "properties": {
    "trigger": {
     "type": "string",
     "enum": [
      "weekly",
      "daily",
      "manual"
     ]
    },
    "engines": {
     "type": "array",
     "items": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40
     },
     "maxItems": 20
    },
    "cost_usd": {
     "type": "number",
     "minimum": 0,
     "maximum": 1000
    },
    "started_at": {
     "type": "string",
     "format": "date-time"
    },
    "finished_at": {
     "type": "string",
     "format": "date-time"
    },
    "status": {
     "type": "string",
     "enum": [
      "done",
      "partial",
      "failed"
     ]
    },
    "notes": {
     "type": "string",
     "maxLength": 2000
    }
   },
   "required": [
    "trigger"
   ],
   "additionalProperties": false
  },
  "results": {
   "type": "array",
   "items": {
    "type": "object",
    "properties": {
     "query": {
      "type": "string",
      "minLength": 3,
      "maxLength": 300
     },
     "engine": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40
     },
     "model_slug": {
      "type": "string",
      "maxLength": 120
     },
     "market": {
      "type": "string",
      "maxLength": 10
     },
     "sample_no": {
      "type": "integer",
      "minimum": 1,
      "maximum": 10
     },
     "status": {
      "type": "string",
      "enum": [
       "cited",
       "mentioned",
       "absent",
       "error",
       "no_ai_answer"
      ]
     },
     "answer_text": {
      "anyOf": [
       {
        "type": "string",
        "maxLength": 20000
       },
       {
        "type": "null"
       }
      ]
     },
     "citations": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "url": {
         "type": "string",
         "maxLength": 2000
        },
        "title": {
         "type": "string",
         "maxLength": 300
        },
        "rank": {
         "type": "integer",
         "minimum": 1,
         "maximum": 100
        }
       },
       "required": [
        "url"
       ],
       "additionalProperties": false
      },
      "maxItems": 100
     },
     "cost_usd": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
     },
     "raw_response": {},
     "probed_at": {
      "type": "string",
      "format": "date-time"
     }
    },
    "required": [
     "query",
     "engine"
    ],
    "additionalProperties": false
   },
   "maxItems": 600
  }
 },
 "required": [
  "tenant_slug",
  "run",
  "results"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `indexing_status` — read-only

**Indexing status.** IndexNow submissions (publish pings to api.indexnow.org — 200/202 = accepted) and the recent publish events for the tenant, optionally for one URL. Says by NAME what IndexNow still needs when it is not wired (INDEXNOW_KEY, primary_hostname, tracking.indexnow_enabled).

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  },
  "url": {
   "type": "string",
   "format": "uri",
   "maxLength": 500
  },
  "limit": {
   "type": "integer",
   "minimum": 1,
   "maximum": 100
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

### `tracking_setup_status` — read-only

**Tracking setup status.** What is configured for this tenant's analytics and what is missing, by NAME: Umami (script slot + API for ingestion), GA4 (BYO, consent-gated), Search Console (property + service account), Bing, IndexNow, the probe query list, the nightly cron. Plus the latest ingested day per source. Never a value, only names and booleans.

Input schema:

```json
{
 "type": "object",
 "properties": {
  "tenant_slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
  }
 },
 "required": [
  "tenant_slug"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output schema:

```json
{
 "type": "object",
 "properties": {
  "ok": {
   "type": "boolean"
  },
  "data": {
   "type": "object",
   "additionalProperties": {}
  },
  "error": {
   "type": "object",
   "properties": {
    "code": {
     "type": "string"
    },
    "message": {
     "type": "string"
    },
    "retryable": {
     "type": "boolean"
    },
    "details": {
     "$ref": "#/properties/data"
    }
   },
   "required": [
    "code",
    "message",
    "retryable"
   ],
   "additionalProperties": false
  },
  "meta": {
   "type": "object",
   "properties": {
    "requestId": {
     "type": "string"
    },
    "contractVersion": {
     "type": "string"
    },
    "generatedAt": {
     "type": "string"
    }
   },
   "required": [
    "requestId",
    "contractVersion",
    "generatedAt"
   ],
   "additionalProperties": false
  }
 },
 "required": [
  "ok",
  "meta"
 ],
 "additionalProperties": false,
 "$schema": "http://json-schema.org/draft-07/schema#"
}
```