Page audit

Before you look at rankings, look at the page. This endpoint fetches a URL the way a browser would and returns everything an on-page review needs: a score built from twelve rules you can read, the elements behind each rule, and the timing and crawl signals that decide whether Google indexes the page at all. It runs on our own servers, so it costs half a credit and answers in a couple of seconds.


POST/v1/pages/audit

Audit a URL

Returns the audit of one page. The score starts at 100 and loses the weight of every check that fails, so two pages with the same score have the same problems. warnings lists things that do not change the score but a human should know, like a noindex or a canonical pointing elsewhere.

Required attributes

  • Name
    url
    Type
    string
    Description

    The page to audit. https:// is assumed when you leave the scheme out, and http:// is upgraded. Private and internal addresses are refused.

Checks and weights

CheckWeight
Title present, 10 to 60 characters12
Meta description, 50 to 160 characters12
Exactly one H110
Served over HTTPS10
Every image has alt text10
Canonical URL declared8
Open Graph tags present8
Valid JSON-LD structured data8
HTML under 200 KB6
Response under 1500 ms6
lang attribute on <html>5
Twitter Card tags present5

Response fields

  • Name
    score
    Type
    integer
    Description

    0 to 100. Each failed check subtracts its weight.

  • Name
    indexable
    Type
    boolean
    Description

    false when the page answers an error, carries noindex, or robots.txt disallows its path.

  • Name
    checks[]
    Type
    array
    Description

    One entry per rule with id, label, weight, passed and a detail that says what was found.

  • Name
    warnings[]
    Type
    array
    Description

    Findings outside the score: noindex, robots.txt blocks, missing viewport, thin content, canonical pointing to another URL.

  • Name
    page
    Type
    object
    Description

    Title and description with their lengths, canonical, robots meta, lang, viewport, word count, headings by level, image and link counts with examples, Open Graph and Twitter tags, JSON-LD types, hreflang alternates.

  • Name
    performance
    Type
    object
    Description

    Time to first byte, total time, HTML size, compression, server header, cache control and how many scripts, stylesheets, iframes and images the HTML references. Measured for the document only, without rendering.

  • Name
    robots_txt
    Type
    object
    Description

    Whether robots.txt exists, the sitemaps it declares, and whether it disallows this path for all crawlers.

Request

POST
/v1/pages/audit
curl -X POST https://suvjexogmfpvxjcktvuk.supabase.co/functions/v1/api/v1/pages/audit \
  -H "Authorization: Bearer $PENGU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://mailchimp.com/"}'

Response

{
  "data": {
    "url": "https://mailchimp.com/",
    "final_url": "https://mailchimp.com/",
    "status": 200,
    "fetched_at": "2026-09-10T07:56:59.897Z",
    "score": 72,
    "indexable": true,
    "checks": [
      { "id": "title", "label": "Title present, 10 to 60 characters", "weight": 12, "passed": true, "detail": "41 characters" },
      { "id": "meta_description", "label": "Meta description, 50 to 160 characters", "weight": 12, "passed": false, "detail": "216 characters" },
      { "id": "single_h1", "label": "Exactly one H1", "weight": 10, "passed": true, "detail": "1 H1 found" },
      { "id": "image_alt", "label": "Every image has alt text", "weight": 10, "passed": false, "detail": "2 of 94 images without alt" },
      { "id": "html_size", "label": "HTML under 200 KB", "weight": 6, "passed": false, "detail": "487.3 KB" },
      // ...
    ],
    "warnings": [],
    "page": {
      "title": "Email Marketing Platform | Mailchimp",
      "title_length": 41,
      "meta_description_length": 216,
      "canonical": "https://mailchimp.com/",
      "robots_meta": "index,follow,archive",
      "lang": "en",
      "word_count": 2395,
      "headings": {
        "h1": ["Email and SMS marketing without the learning curve"],
        "h2": ["Most popular", "Recommended for your business", "..."],
        "counts": { "h1": 1, "h2": 13, "h3": 16, "h4": 28, "h5": 0, "h6": 0 }
      },
      "images": { "total": 94, "missing_alt": 2, "lazy_loaded": 61, "missing_alt_examples": ["..."] },
      "links": { "internal": 212, "external": 9, "nofollow": 0, "external_examples": ["..."] },
      "structured_data": ["WebPage"],
      "hreflang_alternates": 12
    },
    "performance": {
      "ttfb_ms": 212,
      "total_ms": 640,
      "html_size_kb": 487.3,
      "compression": "br",
      "resources": { "scripts": 31, "stylesheets": 4, "iframes": 0, "images": 94 }
    },
    "robots_txt": { "present": true, "sitemaps": ["https://mailchimp.com/sitemap.xml"], "disallows_page": false }
  },
  "meta": {
    "credits_charged": 0.5,
    "credits_remaining": 483,
    "request_id": "6c1d0f7a-2f3b-4f0e-9d21-8a5c4b0e1f22"
  }
}

What it is good for

  • First call of any audit. Run it on the home page and the three pages that matter most before spending credits on rankings. Most quick wins are here.
  • Batch checks from an agent. Ask Claude to list a site's URLs with the sitemap endpoint below, audit the newest fifty and table the scores. At half a credit a page, a hundred pages cost fifty credits.
  • Before and after. Audit, fix, audit again. The checks are deterministic, so a score change means a real change.

Pages are fetched from our servers in Europe. A site that redirects visitors by location will do so here too, and final_url says where you landed; pass the exact localised URL when it matters, for example mailchimp.com/en-us/ instead of mailchimp.com.

It reads the HTML as served, without running JavaScript. A page whose title or content only exists after client-side rendering will look thin here, which is itself worth knowing: Google renders JavaScript, but later and less often.


POST/v1/pages/sitemap

Sitemap URLs

The URLs a site declares in its sitemaps, with their lastmod. Sitemaps are found through robots.txt and the usual locations (/sitemap.xml, /sitemap_index.xml), sitemap indexes are followed, and gzipped files are read. Up to 500 URLs come back, newest first when the site dates them.

Required attributes

  • Name
    site
    Type
    string
    Description

    A domain such as hubspot.com, a site URL, or a direct sitemap URL when you already know it.

Optional attributes

  • Name
    limit
    Type
    integer
    Description

    URLs to return, 10 to 500. Default 100.

Response fields

  • Name
    sitemaps
    Type
    array
    Description

    The sitemap files that were read, in order.

  • Name
    truncated
    Type
    boolean
    Description

    true when the site declares more URLs or child sitemaps than were read. Large sites usually do.

  • Name
    urls[]
    Type
    array
    Description

    url and lastmod (ISO date or null).

Request

POST
/v1/pages/sitemap
curl -X POST https://suvjexogmfpvxjcktvuk.supabase.co/functions/v1/api/v1/pages/sitemap \
  -H "Authorization: Bearer $PENGU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"site": "hubspot.com", "limit": 50}'

Response

{
  "data": {
    "site": "https://www.hubspot.com",
    "sitemaps": [
      "https://www.hubspot.com/sitemap.xml",
      "https://www.hubspot.com/sitemap-products.xml"
    ],
    "count": 50,
    "truncated": true,
    "urls": [
      { "url": "https://www.hubspot.com/products/crm", "lastmod": "2026-09-08" },
      { "url": "https://www.hubspot.com/products/marketing", "lastmod": "2026-09-08" },
      // ...
    ]
  },
  "meta": {
    "credits_charged": 0.5,
    "credits_remaining": 482.5,
    "request_id": "4a7f2c19-8e3d-4b6a-9f10-2c5d7e8f9a01"
  }
}

Was this page helpful?