Brief

Brief

The brief is a page's work packet — everything needed to start working on a page, in one call, instead of five or six. Start here when an agent picks a page to work on (e.g. from opportunities): it saves the round-trips of separately fetching the page, its content, its analysis, its keywords, its GSC queries, its outline, and its links.

Scope: sites:read


GET /pages/{pageId}/brief

Path parameters

Parameter Type Description
pageId integer The page's ID

Request

curl -s https://new.metamonster.ai/api/v1/pages/5001/brief \
  -H "Authorization: Bearer mm_YOUR_API_KEY"

Sections degrade independently

The page itself is the only hard dependency — 404 if it doesn't exist or isn't yours. Every other section is fetched in parallel and degrades on its own failure rather than failing the whole call: a GSC outage shows up as queries.gsc_status: "unavailable", a page with no analysis yet shows analysis: null, and so on. Never assume a 200 means every section succeeded — check each section's own status/null-ness.

Section What it is Same data standalone at How it degrades
page Page summary (id, url, path, title, primary keyword, priority, opportunity score). GET /pages/{pageId} (fuller detail) N/A — required; the brief 404s if the page doesn't resolve.
fields Resolved current value + status for title, meta_description, h1, schema, body — draft if one exists, else the live/snapshot value. fields.schema also carries validation (JSON-LD check against the saved value); fields.body also carries content_version_id. GET /pages/{pageId}/drafts, GET /pages/{pageId}/content On failure, all five fields are null.
analysis Latest non-skipped analysis with its components, estimated_score, estimated_grade, or null. GET /pages/{pageId}/analysis null if there's no analysis yet, or on a load failure — you can't tell those apart from the brief alone; if it matters, call /analysis directly.
recommendations Open recommendations only (pending/in_progress — no applied/dismissed history), each with content_excerpt. GET /pages/{pageId}/analysis (recommendations, includes closed ones too) Empty array on failure or when there's no analysis.
keywords primary and secondary keywords with cached metrics (search volume, difficulty, intent), or null metrics on a cache miss per keyword. GET /keywords/metrics (per keyword) Falls back to keywords with metrics: null on a lookup failure — the keyword strings themselves still come from the page record, not the failed call.
queries Live GSC top queries for a fixed 28-day window (same shape as the standalone endpoint). GET /pages/{pageId}/queries (any window) gsc_status covers this — not_connected/not_indexed/unavailable as usual. Use the standalone endpoint if you need a custom window.
outline A summary of the latest outline: {id, status, primary_keyword, section_count, completed_at} — not the full sections/SERP snapshot. GET /pages/{pageId}/outline (full sections + SERP snapshot) null if there's no outline yet, or on a load failure.
links Outbound links extracted from the latest crawl (same shape as the standalone endpoint, same 1000-row cap). GET /pages/{pageId}/links {data: [], meta: {total: 0, truncated: false}} on failure.

Body size

fields.body.value is the page's full body content rendered as markdown — the same string GET /pages/{pageId}/content returns, which can run large for long-form pages (up to the API's 1,000,000-character save limit). If you only need metadata fields, or you're processing many pages in a batch, be mindful of response size; there's no way to omit fields.body from the brief.

fields.body.content_version_id

The page's current content version id (null if it has none yet). Pass it directly as base_version_id on POST /pages/{pageId}/content — no need to call GET /pages/{pageId}/content first just to get a version to guard against.

When content_excerpt is null

recommendations[].content_excerpt is null in two cases: the recommendation is an add-type rec with no target nodes (nothing existing to excerpt), or a crawl/recrawl happened since the analysis ran and regenerated the document's node ids, so the recommendation's stored target_node_ids no longer resolve. In the second case, POST /pages/{pageId}/analyze to refresh targeting against the current document.

Response 200 (trimmed)

{
  "data": {
    "page": { "id": 5001, "url": "https://example.com/pricing", "path": "/pricing", "site_id": 42, "title": "Pricing", "primary_keyword": "example pricing", "is_priority": true, "opportunity_score": 68 },
    "fields": {
      "title": { "value": "Pricing", "status": "snapshot" },
      "meta_description": { "value": "See plans and pricing for Example.", "status": "snapshot" },
      "h1": { "value": "Pricing", "status": "snapshot" },
      "schema": { "value": "{\"@context\":\"https://schema.org\",\"@type\":\"Organization\",\"name\":\"Example\"}", "status": "draft", "validation": { "valid": true, "errors": [], "warnings": ["Missing recommended property \"logo\" for Organization"], "summary": "Schema is valid but has 1 warning to consider." } },
      "body": { "value": "# Pricing\n\nChoose a plan that fits your team…", "status": "snapshot", "content_version_id": 287 }
    },
    "analysis": {
      "id": 7200,
      "overall_score": 68,
      "overall_grade": "C+",
      "estimated_score": 76,
      "estimated_grade": "B-",
      "status": "completed",
      "completed_at": "2026-08-10T09:00:00Z",
      "score_source": "audit",
      "content_stale": false,
      "components": [
        { "id": 1, "analysis_id": 7200, "component_type": "title", "label": "Title", "status": "completed", "score": 60, "grade": "C", "estimated_score": 72, "estimated_grade": "B-", "criteria_scores": {}, "rationale": "Keyword appears late in the title.", "model": "claude-haiku", "site_id": 42, "created_at": "2026-08-10T09:00:00Z", "updated_at": "2026-08-10T09:00:00Z" }
      ]
    },
    "recommendations": [
      { "id": 5500, "analysis_id": 7200, "page_id": 5001, "site_id": 42, "title": "Lead the title with the primary keyword", "impact": "medium", "status": "pending", "target_field": "title", "content_excerpt": null, "…": "…other Recommendation fields" }
    ],
    "keywords": {
      "primary": { "keyword": "example pricing", "metrics": { "keyword": "example pricing", "search_volume": 2400, "keyword_difficulty": 32.5, "cpc": 4.1, "competition": 0.6, "search_intent": "commercial", "monthly_searches": null } },
      "secondary": []
    },
    "queries": {
      "data": [{ "query": "example pricing", "clicks": 64, "impressions": 1800, "ctr": 0.0356, "position": 8.2 }],
      "gsc_status": "available",
      "window": { "start_date": "2026-07-17", "end_date": "2026-08-13" }
    },
    "outline": { "id": 901, "status": "completed", "primary_keyword": "example pricing", "section_count": 6, "completed_at": "2026-08-10T12:00:42Z" },
    "links": { "data": [], "meta": { "total": 0, "truncated": false } }
  }
}

fields.*.status mirrors draft status values (snapshot = no draft, serving the crawled/current value; draft/pending/generating/failed otherwise). recommendations[].content_excerpt is populated when resolvable from the current document, same rule as GET /pages/{pageId}/analysis.

fields.schema.validation

fields.schema.validation is the same {valid, errors[], warnings[], summary} shape PUT /pages/{pageId}/drafts/schema returns, computed fresh against whatever fields.schema.value currently resolves to (draft if one exists, else the crawled snapshot's schema). It's null when fields.schema.value is null or an empty string — there's nothing to validate. Note that a page with no schema at all still returns a fields.schema object, not null: {"value": null, "status": "snapshot", "validation": null}. Check fields.schema.value, not fields.schema, to decide whether a page has schema. (fields.schema itself is only null in the failure case, where every field is null.) This is the fastest way to check schema health without a separate call: if value is non-null and validation.valid is false, read validation.errors for what to fix, PUT a corrected draft_value, then resolve any open schema-target recommendation with no body — see Recommendations.

Errors

Status When
401 unauthorized Missing/invalid key
403 forbidden Key lacks sites:read
404 not_found No such page in your organization — the only hard failure the brief can produce
429 rate_limited Rate limit exceeded