Site planning

Site planning

A planned site is a site that isn't live yet. You describe the business in a brief, MetaMonster proposes the pages the site needs, assigns each a primary keyword, and you build the content before launch. When the site is published, going live crawls it and matches the crawled URLs to the planned pages by path, so their keywords, briefs and content carry over. This is the same flow as the dashboard's Plan a site.

Planned pages are ordinary pages with planned: true and no URL. Briefs (POST /pages/{pageId}/outline), body content (POST /pages/{pageId}/content) and drafts all work on them.

Scope: sites:read for GET, content:write for everything else.

The flow:

  1. POST /sites/plan creates the planned site.
  2. POST /sites/{siteId}/plan/generate proposes the page set.
  3. POST, PATCH and DELETE on /sites/{siteId}/plan/pages refine it.
  4. POST /sites/{siteId}/plan/keywords assigns keywords; poll GET /sites/{siteId}/plan.
  5. POST /sites/{siteId}/go-live once the site is published.

POST /sites/plan

Create a planned site. No domain is needed and nothing is crawled.

Body

Field Type Description
brief string Required, up to 20,000 chars. What the business does, who it serves, where, and its services or products.
name string Optional. A working name, or the domain the site will launch on. Derived from the brief when omitted.
location_country_code integer Optional. DataForSEO country code for keyword research. Derived from the brief when omitted (US if unclear).
location_country_name string Optional.

Without name or a location, one model call derives them, so this route spends the llm rate bucket (6 requests per 60 seconds). An unsubscribed organization that already has a site gets 402 payment_required.

curl -s -X POST "https://new.metamonster.ai/api/v1/sites/plan" \
  -H "Authorization: Bearer mm_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"brief": "Family-owned roofing company in Austin, TX. Repairs, replacements and storm damage for homes."}'

Response 201

{ "data": SiteDetail } (see Sites). url is null until the site goes live.


POST /sites/{siteId}/plan/generate

Propose the page set from the brief, and structure the brief into the site's business_context and voice_tone. Idempotent: when planned pages already exist they are returned without a model call.

Field Type Description
regenerate boolean Optional. Replace the whole set with a fresh proposal.
brief string Optional. A revised brief; it replaces the stored one before generating.

Generating takes two model calls (typically 10-30 seconds) and spends the llm rate bucket.

Response 200

{
  "data": {
    "pages": [
      { "id": 501, "name": "Home", "slug": "", "purpose": "Introduce the company and route visitors to services" },
      { "id": 502, "name": "Roof Repair", "slug": "services/roof-repair", "purpose": "Explain repair services and get quote requests" }
    ],
    "brief": "Family-owned roofing company in Austin, TX. ..."
  }
}

GET /sites/{siteId}/plan

The planned pages and keyword progress. Spends the jobs rate bucket, so it is safe to poll.

Response 200

{
  "data": {
    "total": 2,
    "with_keyword": 1,
    "failed": 0,
    "done": false,
    "keywords_running": true,
    "crawl_status": null,
    "pages": [
      { "id": 501, "name": "Home", "path": "/", "purpose": "...", "primary_keyword": "austin roofing company", "failed": false },
      { "id": 502, "name": "Roof Repair", "path": "/services/roof-repair", "purpose": "...", "primary_keyword": null, "failed": false }
    ]
  }
}

failed counts pages whose keyword assignment terminally failed. crawl_status is null until the site goes live, then the status of its latest crawl.


POST /sites/{siteId}/plan/pages

Add a planned page. Body: { name, slug?, purpose? }. The slug is normalized like proposed paths (About Us becomes about-us, home is the homepage) and made unique; without one it is the slugified name. Response 201: { "data": { id, name, slug, purpose } }.

PATCH /sites/{siteId}/plan/pages/{pageId}

Edit a planned page. Body: at least one of name, slug, purpose. Response 200: the updated page. 404 for a page that isn't a planned page of this site.

DELETE /sites/{siteId}/plan/pages/{pageId}

Remove a planned page (soft delete; its slug is freed). Response 200: { "data": { "id": 502, "deleted": true } }.


POST /sites/{siteId}/plan/keywords

Assign a primary keyword to every planned page that has none yet. Each is picked from the page's name and purpose and the site's business context, checked against search data. Spends credits per page, as in the dashboard.

Queued: answers 202 { "data": { "started": true } } at once. Poll GET /sites/{siteId}/plan until keywords_running is false. Nothing new is queued while an assignment is running (already_running: true).


POST /sites/{siteId}/go-live

Take the site live. Body: { "domain": "acmeroofing.com" } (a scheme or path is stripped; a plain name is 400). The domain replaces the working name and a crawl starts.

Response 202

{ "data": { "crawl_id": 812, "status": "pending", "domain": "acmeroofing.com" } }

409 conflict when the site is already live or a crawl is running. Follow the crawl with GET /sites/{siteId} (latest_crawl) or GET /sites/{siteId}/plan (crawl_status).