Sites

Sites

Subscription required. Every endpoint on this page but GET /sites needs an active subscription. Without one the request is refused with 402 payment_required (Pick a plan to keep going.) before the endpoint runs, reads included. GET /sites answers with or without one. See Conventions.

A site is a website you've added to MetaMonster. Sites are the top of the hierarchy — pages, snapshots, analysis, and drafts all belong to a site.

All site endpoints require the sites:read scope.

Site-scoped operations over pages are documented with the pages they act on: GET /sites/{siteId}/pages, PATCH /sites/{siteId}/pages (bulk edit), and POST /sites/{siteId}/analyze (bulk analyze, content:write).


GET /sites

List your organization's sites, newest first. Paginated.

Scope: sites:read

Query parameters

Parameter Type Default Notes
page integer 1 1-indexed. Minimum 1.
limit integer 15 Items per page. Minimum 1, maximum 100.

Request

curl "https://app.metamonster.ai/api/v1/sites?page=1&limit=15" \
  -H "Authorization: Bearer mm_YOUR_API_KEY"

Response 200

{
  "data": [
    {
      "id": 42,
      "domain": "example.com",
      "url": "https://example.com",
      "name": "Example",
      "created_at": "2026-05-01T12:00:00Z",
      "last_crawled_at": "2026-07-20T09:30:00Z",
      "page_count": 128,
      "draft_count": 5
    }
  ],
  "meta": { "total": 1, "page": 1, "per_page": 15 }
}

Each item (site summary shape):

Field Type Description
id integer Site ID
domain string The site's domain
url string | null The site's homepage URL, derived from domain: https:// + the lowercased domain, with no trailing slash (e.g. https://example.com). null when domain is not a valid bare hostname — a planned (pre-launch) site whose domain holds a display name, a missing domain, or a domain that includes a path.
name string | null Display name
created_at string | null When the site was added (ISO 8601)
last_crawled_at string | null When the site was last crawled — the latest crawl's completion time, or its start time if still running, else null
page_count integer Number of pages discovered
draft_count integer Number of active field drafts across the site

POST /sites

Add a site to your organization and queue the standard setup job (page discovery → GSC enrichment → crawl) — the API twin of the dashboard's add-site form, minus GSC wiring (API keys carry no user; connect Google Search Console from the dashboard).

Scope: content:write

Request body

Field Type Default Notes
domain string — Required. With or without a scheme/trailing slash (e.g. example.com or https://example.com/).
auto_run_keyword_scope string homepage One of homepage, top, all. Have the setup job also generate keywords for this scope. Keywords are free and always cover at least the homepage, so none is treated as homepage.
auto_run_analysis_scope string none One of none, homepage, top, all. Have the setup job also run analysis for this scope.

Both scopes default to none so adding a site over the API never spends keyword or analysis allowance by itself — pass a wider scope only if you want the setup job to run keyword generation and/or analysis right away.

Adding a site requires an active subscription. An org without one gets 402 payment_required and no site is created; there is no free site and no free audit.

Request

curl -X POST https://app.metamonster.ai/api/v1/sites \
  -H "Authorization: Bearer mm_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "example.com" }'

Response 201

{
  "data": {
    "id": 42,
    "domain": "example.com",
    "url": "https://example.com",
    "name": null,
    "business_context": null,
    "voice_tone": null,
    "gsc_connected": false,
    "gsc_property_url": null,
    "background_status": "discovering",
    "score_curve": null,
    "location": { "country_code": null, "country_name": null, "city_code": null, "city_name": null },
    "llms_txt": null,
    "setup_completed_at": null,
    "discovery_completed_at": null,
    "created_at": "2026-08-19T00:00:00Z",
    "updated_at": "2026-08-19T00:00:00Z",
    "last_crawled_at": null,
    "latest_crawl": null,
    "page_count": 0,
    "draft_count": 0
  }
}

Same site detail shape as GET /sites/{siteId}. background_status starts at discovering — poll GET /sites/{siteId} until it returns to null before expecting a full page list or opportunity scores.

Errors

Status When
400 invalid_request domain is missing or not a valid domain
402 payment_required The org has no active subscription (message: Pick a plan to keep going.)

GET /sites/{siteId}

Fetch a single site with full detail, including its latest crawl.

Scope: sites:read

Path parameters

Parameter Type Description
siteId integer The site's ID

Request

curl https://app.metamonster.ai/api/v1/sites/42 \
  -H "Authorization: Bearer mm_YOUR_API_KEY"

Response 200

{
  "data": {
    "id": 42,
    "domain": "example.com",
    "url": "https://example.com",
    "name": "Example",
    "business_context": "B2B SaaS for SEO teams",
    "voice_tone": "Professional, concise",
    "gsc_connected": true,
    "gsc_property_url": "sc-domain:example.com",
    "background_status": null,
    "score_curve": "proportional",
    "location": {
      "country_code": 2840,
      "country_name": "United States",
      "city_code": null,
      "city_name": null
    },
    "llms_txt": "…",
    "setup_completed_at": "2026-05-01T12:30:00Z",
    "discovery_completed_at": "2026-05-01T13:00:00Z",
    "created_at": "2026-05-01T12:00:00Z",
    "updated_at": "2026-07-20T09:30:00Z",
    "last_crawled_at": "2026-07-20T09:30:00Z",
    "latest_crawl": {
      "id": 900,
      "status": "completed",
      "total_pages": 128,
      "crawled_pages": 128,
      "started_at": "2026-07-20T09:00:00Z",
      "completed_at": "2026-07-20T09:30:00Z"
    },
    "page_count": 128,
    "draft_count": 5
  }
}

Site detail shape:

Field Type Description
id integer Site ID
domain string The site's domain
url string | null The site's homepage URL, derived from domain: https:// + the lowercased domain, with no trailing slash (e.g. https://example.com). null when domain is not a valid bare hostname — a planned (pre-launch) site whose domain holds a display name, a missing domain, or a domain that includes a path.
name string | null Display name
business_context string | null Business context used to guide content generation
voice_tone string | null Voice & tone guidance
gsc_connected boolean | null Whether Google Search Console is connected
gsc_property_url string | null The connected GSC property
background_status string | null Non-null while site setup / crawling / keyword generation runs in the background: discovering, enriching, crawling, generating_context. null = idle. Opportunity scores and page lists may be incomplete until it returns to null.
score_curve string | null Site-level grade-curve setting
location object Target market — { country_code, country_name, city_code, city_name }, each nullable
llms_txt string | null Generated llms.txt content
setup_completed_at string | null When site setup finished
discovery_completed_at string | null When page discovery finished
created_at string | null When the site was added
updated_at string | null Last update
last_crawled_at string | null Derived from latest_crawl (completion, else start, else null)
latest_crawl object | null The most recent crawl (see below), or null if never crawled
page_count integer Number of pages discovered
draft_count integer Number of active field drafts across the site

latest_crawl object:

Field Type Description
id integer Crawl ID
status string Crawl status (e.g. completed, running, failed)
total_pages integer | null Pages the crawl intended to fetch
crawled_pages integer | null Pages fetched so far
started_at string | null When the crawl started
completed_at string | null When the crawl finished

Errors

Status When
404 not_found No site with that ID in your organization (message: Site not found)

GET /sites/{siteId}/email-report-settings

Internal preview: 404 not_found unless email reports are enabled for your organization.

Whether the site's monthly email report is on, and who it goes to. Scope: sites:read.

Request

curl https://app.metamonster.ai/api/v1/sites/42/email-report-settings \
  -H "Authorization: Bearer $METAMONSTER_API_KEY"

Response 200

{
  "data": {
    "enabled": true,
    "recipients": ["jade@client.com", "owner@client.com"]
  }
}
Field Type Description
enabled boolean The monthly report is on. Off by default.
recipients string[] Who gets it: lowercased, no duplicates, at most 10.

Errors

Status When
404 not_found No site with that ID in your organization, or email reports aren't enabled for it

PUT /sites/{siteId}/email-report-settings

Internal preview: 404 not_found unless email reports are enabled for your organization.

Turn the monthly email report on or off and/or set who it goes to. Scope: content:write.

Request body

Give enabled, recipients or both.

Field Type Description
enabled boolean true sends the monthly report; false stops it.
recipients string[] The full list of addresses. It replaces the current list. Trimmed, lowercased and de-duplicated; at most 10.

Turning the report on with no recipients is allowed: nothing is sent until one is added.

Request

curl -X PUT https://app.metamonster.ai/api/v1/sites/42/email-report-settings \
  -H "Authorization: Bearer $METAMONSTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true, "recipients": ["Jade@Client.com", "owner@client.com"] }'

Response 200

The saved settings, as GET returns them.

Errors

Status When
400 invalid_request An invalid address (the message names it, e.g. "bob@" isn't a valid email address.), more than 10 recipients, or neither field given
404 not_found No site with that ID in your organization, or email reports aren't enabled for it