Report
The report is the brief plus two extra sections: comparison (did the page's score move since the last completed analysis?) and serp (what the SERP looks like for this page's primary keyword). It's the single call to read the result of an analyze run — use this instead of separately calling /brief, then /analysis?compare=prev, then /outline for the SERP snapshot.
Scope: sites:read
GET /pages/{pageId}/report
Path parameters
| Parameter | Type | Description |
|---|---|---|
pageId |
integer | The page's ID |
Request
curl -s https://new.metamonster.ai/api/v1/pages/5001/report \
-H "Authorization: Bearer mm_YOUR_API_KEY"
When to use /report vs /brief
Both bundle the page's work packet — page, fields, analysis, recommendations, keywords, queries, outline, links — with sections degrading independently and only the page itself able to 404. /report's response is a superset: everything /brief returns, plus comparison, comparison_note, and serp. Reach for /brief when you're about to make edits (it's the smaller payload); reach for /report right after an analyze job completes, when you want to know what changed and how the page ranks in search — the MCP analyze_page/get_page_report tools return exactly this shape.
The two extra keys
| Section | What it is | Same data standalone at | How it degrades |
|---|---|---|---|
comparison |
Score/component/criterion deltas plus a best-effort recommendation diff against the previous completed analysis. null when there's nothing to compare — see comparison_note. |
GET /pages/{pageId}/analysis?compare=prev |
null (with comparison_note set) if there's no analysis yet, no earlier completed analysis, or the latest analysis hasn't completed. |
comparison_note |
Present (non-null) only when comparison is null. no_previous_analysis = no earlier completed analysis to diff against (including a page's first-ever analysis, or no analysis at all). analysis_incomplete = the latest analysis hasn't finished, so it has no scores to compare. |
— | N/A |
serp |
{ analysis, snapshot, source } — the current analysis's serp_analysis component (public columns only) plus the latest outline's SERP snapshot (top results + competitor headings), composed from data already on hand. No live SERP fetch happens here. |
analysis mirrors the serp_analysis entry in /analysis's components; snapshot is GET /pages/{pageId}/outline's serp_snapshot |
analysis: null if there's no analysis or it has no serp_analysis component; snapshot: null if there's no outline (or the outline load fails); source: "none" when both are null. |
serp.source tells you which half you got without checking both fields yourself: "analysis+outline" (both present), "analysis" (component only), "outline" (snapshot only), "none" (neither).
Response 200 (trimmed — see brief.md for the shared page/fields/analysis/recommendations/keywords/queries/outline/links shape)
{
"data": {
"page": { "id": 5001, "url": "https://example.com/pricing", "path": "/pricing" },
"fields": { "…": "…same shape as the brief" },
"analysis": { "id": 7201, "overall_score": 74, "overall_grade": "B-", "components": [ { "id": 9, "analysis_id": 7201, "component_type": "serp_analysis", "score": 80, "…": "…" } ] },
"recommendations": [],
"keywords": { "primary": { "keyword": "example pricing", "metrics": null }, "secondary": [] },
"queries": { "data": [], "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 } },
"comparison": {
"previous_analysis_id": 7200,
"previous_analyzed_at": "2026-08-10T09:00:00Z",
"overall_score": { "prev": 68, "now": 74, "delta": 6 },
"overall_grade": { "prev": "C+", "now": "B-" },
"components": [ { "component_type": "title", "score": { "prev": 60, "now": 72, "delta": 12 } } ],
"criteria": [],
"recommendations": { "new": [], "still_open": [], "resolved": [], "dropped": [] },
"matching": "best_effort_title"
},
"comparison_note": null,
"serp": {
"analysis": { "id": 9, "analysis_id": 7201, "component_type": "serp_analysis", "score": 80, "grade": "B", "estimated_score": null, "estimated_grade": null, "criteria_scores": {}, "rationale": "…", "model": "claude-haiku", "site_id": 42, "created_at": "2026-08-15T09:00:00Z", "updated_at": "2026-08-15T09:00:00Z", "label": "SERP Analysis", "status": "completed" },
"snapshot": {
"keyword": "example pricing",
"results": [ { "position": 1, "title": "Example Pricing Plans", "url": "https://competitor.com/pricing", "snippet": "Compare plans…" } ],
"competitors": [ { "position": 1, "title": "Example Pricing Plans", "url": "https://competitor.com/pricing", "domain": "competitor.com", "headings": [ { "level": 1, "text": "Pricing" } ], "word_count": 1200, "fetch_error": false } ]
},
"source": "analysis+outline"
}
}
}
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 report can produce |
429 rate_limited |
Rate limit exceeded |