Sites
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://new.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 | none |
One of none, homepage, top, all. Have the setup job also generate keywords for this scope. |
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.
Unsubscribed orgs are capped at one free site-run, and have any wider scope clamped down rather than rejected: all keywords → top; top/all analysis → homepage. This mirrors the free-tier rules the dashboard's add-site form enforces.
Request
curl -X POST https://new.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 |
Unsubscribed org has already used its one free site-run (message: Start your free trial to add more sites.) |
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://new.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) |