{
  "openapi": "3.0.1",
  "info": {
    "title": "Bulk PageSpeed Insights & Core Web Vitals Checker + Monitor",
    "description": "Core Web Vitals and PageSpeed bulk checker: run PageSpeed Insights (Lighthouse) on many URLs, a sitemap or a whole site. No API key needed. Real-user LCP, INP, CLS pass/fail with 6-month trends, top fixes, competitor comparison, Slack/Discord alerts and an HTML report.",
    "version": "0.1",
    "x-build-id": "yfbgLGvEb8uGik4ms"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/tobbyb~pagespeed-core-web-vitals-monitor/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-tobbyb-pagespeed-core-web-vitals-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/tobbyb~pagespeed-core-web-vitals-monitor/runs": {
      "post": {
        "operationId": "runs-sync-tobbyb-pagespeed-core-web-vitals-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/tobbyb~pagespeed-core-web-vitals-monitor/run-sync": {
      "post": {
        "operationId": "run-sync-tobbyb-pagespeed-core-web-vitals-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "urls": {
            "title": "URLs to test",
            "type": "array",
            "description": "Pages to test, one URL per line (use Bulk edit to paste hundreds). Bare domains like example.com get https:// added. Duplicates are removed. Invalid entries become error items and never stop the run.",
            "items": {
              "type": "string"
            }
          },
          "sitemapUrls": {
            "title": "Sitemaps or site URLs (optional)",
            "type": "array",
            "description": "Sitemap URLs to expand into pages (sitemap indexes, .xml.gz and plain-text sitemaps are supported, and huge sitemaps are streamed). You can also enter just a site, e.g. https://www.example.com: the Actor finds its sitemap through robots.txt. A broken sitemap URL also falls back to robots.txt.",
            "items": {
              "type": "string"
            }
          },
          "maxSitemapUrls": {
            "title": "Max pages taken from sitemaps",
            "minimum": 1,
            "maximum": 50000,
            "type": "integer",
            "description": "Upper limit of page URLs taken from all sitemaps together (keeps costs predictable).",
            "default": 50
          },
          "sitemapIncludePattern": {
            "title": "Sitemap include pattern (regex)",
            "type": "string",
            "description": "Only take sitemap URLs matching this regular expression, e.g. /blog/"
          },
          "sitemapExcludePattern": {
            "title": "Sitemap exclude pattern (regex)",
            "type": "string",
            "description": "Skip sitemap URLs matching this regular expression, e.g. \\.pdf$|/tag/"
          },
          "device": {
            "title": "Device",
            "enum": [
              "mobile",
              "desktop",
              "both"
            ],
            "type": "string",
            "description": "Run the audit for mobile, desktop, or both. 'Both' produces two results (and two charges) per URL.",
            "default": "mobile"
          },
          "competitorUrls": {
            "title": "Competitor URLs (compare mode)",
            "type": "array",
            "description": "Optional. Competitor pages to test the same way, e.g. their homepage and key landing pages. Results get role = competitor, and the report and SUMMARY rank every site side by side (performance, Core Web Vitals pass rate, real-user LCP/INP). Competitors are charged like your pages and are not monitored for regressions.",
            "items": {
              "type": "string"
            }
          },
          "pageGroups": {
            "title": "Page groups (rollups)",
            "type": "array",
            "description": "Optional. Group pages by template to see which part of the site is slow, one per line as Name = regex, e.g. 'Blog = /blog/' or 'Products = ^/(p|product)/'. The first match wins. Without groups, pages are grouped by the first folder of the path. Rollups (pass rate, medians, worst page) are in SUMMARY and the report.",
            "items": {
              "type": "string"
            }
          },
          "categories": {
            "title": "Lighthouse categories",
            "type": "array",
            "description": "Lighthouse categories to score. All four cost the same.",
            "items": {
              "type": "string",
              "enum": [
                "performance",
                "accessibility",
                "best-practices",
                "seo"
              ],
              "enumTitles": [
                "Performance",
                "Accessibility",
                "Best practices",
                "SEO"
              ]
            },
            "default": [
              "performance",
              "accessibility",
              "best-practices",
              "seo"
            ]
          },
          "googleApiKey": {
            "title": "Google API key (optional)",
            "type": "string",
            "description": "Not needed: without a key the Actor uses its own shared key, limited to 200 page audits per run. Add your own free key (PageSpeed Insights API + Chrome UX Report API enabled) for big jobs, frequent schedules or your own ~25,000 audits/day. It is stored encrypted and never appears in the output. See README → 'Use your own Google API key'."
          },
          "includeFieldData": {
            "title": "Include real-user field data (CrUX)",
            "type": "boolean",
            "description": "Adds Chrome UX Report p75 LCP, INP, CLS, FCP and TTFB for the page and its origin, plus the Core Web Vitals pass/fail assessment.",
            "default": true
          },
          "includeFieldHistory": {
            "title": "Include 6-month field trend (CrUX History)",
            "type": "boolean",
            "description": "Adds weekly real-user p75 LCP, INP, CLS, FCP and TTFB for up to 40 weeks from the Chrome UX Report History API, with a trend (improving / stable / worsening), the current pass/fail streak and when it started. Free, about 1 extra second per page.",
            "default": true
          },
          "fieldHistoryWeeks": {
            "title": "Trend length (weeks)",
            "minimum": 1,
            "maximum": 40,
            "type": "integer",
            "description": "How many weekly data points to fetch (each one is a 28-day window). 25 ≈ 6 months, 40 = maximum.",
            "default": 25
          },
          "labData": {
            "title": "Run Lighthouse lab test",
            "type": "boolean",
            "description": "Turn off for the fast, low-cost bulk Core Web Vitals checker: real-user Chrome UX Report data only (no Lighthouse), about 1 second per URL, ideal for checking thousands of pages or competitor domains. Pages without CrUX data are not charged.",
            "default": true
          },
          "labRuns": {
            "title": "Lighthouse runs per page (median)",
            "minimum": 1,
            "maximum": 5,
            "type": "integer",
            "description": "Lab scores vary from run to run (we measured up to 18 points on unchanged pages). Run Lighthouse 2–5 times per page and report the MEDIAN of every score and metric plus the min–max spread (labRuns.spread). Default: 1, or 3 when monitoring with lab alerts turned on. Each extra run is charged as 'extra lab run'."
          },
          "monitorName": {
            "title": "Monitor name (enables regression alerts)",
            "type": "string",
            "description": "Give this URL set a name, e.g. 'acme-shop-weekly', and schedule the Actor. Each run is compared with earlier runs of the same monitor (kept in a named key-value store). By default you are alerted when real-user Core Web Vitals get worse; lab changes are listed as info. Don't run two schedules with the same name at the same time: the Actor detects overlap, warns and merges instead of overwriting."
          },
          "labAlerts": {
            "title": "Also alert on lab (Lighthouse) changes",
            "type": "boolean",
            "description": "Off by default: alerts and the 'regression' charge come only from real-user Chrome UX Report data (field p75 LCP/INP/CLS/FCP/TTFB getting worse, a rating downgrade, or Core Web Vitals PASSED → FAILED). Lab score changes still appear in every row as info (labChanges, labChangeSummary). Turn this on to also alert on lab drops; then 'Lighthouse runs per page' defaults to 3 (median) and a lab drop must repeat on 2 runs in a row. Why off: Google's lab tests are noisy. In our live tests on 30 unchanged sites, lab-based alerting still raised 2–7 false alerts per run.",
            "default": false
          },
          "scoreDropThreshold": {
            "title": "Score drop threshold (points)",
            "minimum": 1,
            "maximum": 100,
            "type": "integer",
            "description": "Only used when 'Also alert on lab changes' is on (it always shapes the info-only labChanges list). Flag a regression when a Lighthouse score (0–100) drops by at least this many points. In live tests, PageSpeed lab scores for the same page varied by a median of 2 and up to 18 points between runs 10 minutes apart.",
            "default": 15
          },
          "labMetricThresholdPct": {
            "title": "Lab metric threshold (%)",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Only used when 'Also alert on lab changes' is on (it always shapes the info-only labChanges list). Flag a regression when a lab metric (LCP, FCP, TBT, CLS, Speed Index) gets worse by at least this percentage and by a minimum absolute amount (LCP/FCP +500 ms, TBT +150 ms, CLS +0.05, Speed Index +1 s). Lab TTFB is not alerted on: Google measures it from its own datacenter.",
            "default": 25
          },
          "labRequireRatingChange": {
            "title": "Lab alerts need a rating downgrade",
            "type": "boolean",
            "description": "Only used when 'Also alert on lab changes' is on (it always shapes the info-only labChanges list). When on (recommended), a lab metric only counts as a regression if its rating also gets worse (good → needs improvement → poor). PageSpeed lab data is noisy. In live tests, turning this off flagged 14 of 46 unchanged pages between two runs; with it on, 4 of 46.",
            "default": true
          },
          "confirmLabRegressions": {
            "title": "Confirm lab regressions on 2 runs in a row",
            "type": "boolean",
            "description": "Only used when 'Also alert on lab changes' is on (it always shapes the info-only labChanges list). Recommended. Lab results are compared with the median of your earlier runs. A lab-only drop must show up in two runs in a row before it is alerted and charged; a one-off dip is listed as 'suspected' instead. Field (real-user) data and Core Web Vitals pass→fail changes always alert at once. In 3 live test runs of 30 unchanged sites this cut false alerts from 19 to 2.",
            "default": true
          },
          "fieldMetricThresholdPct": {
            "title": "Field metric threshold (%)",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Flag a regression when a real-user p75 metric gets worse by at least this percentage. A Core Web Vitals pass→fail or a good→poor rating change is always flagged.",
            "default": 10
          },
          "alertWebhookUrl": {
            "title": "Alert webhook URL (Slack, Discord, Teams)",
            "type": "string",
            "description": "Get a message when pages get slower. Paste a Slack Incoming Webhook (hooks.slack.com/…), a Discord webhook (discord.com/api/webhooks/…), a Microsoft Teams Workflows webhook, or any https endpoint (it receives JSON). Stored encrypted."
          },
          "alertOn": {
            "title": "Send alert when",
            "enum": [
              "regressions",
              "regressions-or-cwv-failures",
              "every-run"
            ],
            "type": "string",
            "description": "When the webhook is called. Without a monitor name there are no regressions, so use one of the other options.",
            "default": "regressions"
          },
          "generateHtmlReport": {
            "title": "Save HTML summary report",
            "type": "boolean",
            "description": "Saves a shareable HTML report (key REPORT in the run's key-value store). Charged once per run.",
            "default": true
          },
          "saveScreenshots": {
            "title": "Save page screenshots",
            "type": "boolean",
            "description": "Saves the above-the-fold screenshot Lighthouse took (about 20 KB JPEG per page) to the run's key-value store. The output gets a `screenshotUrl` and the HTML report shows thumbnails. Free.",
            "default": true
          },
          "maxOpportunities": {
            "title": "Top opportunities per page",
            "minimum": 0,
            "maximum": 20,
            "type": "integer",
            "description": "How many improvement opportunities (with estimated savings) to include per result.",
            "default": 10
          },
          "maxFailedAudits": {
            "title": "Failed audits per category",
            "minimum": 0,
            "maximum": 50,
            "type": "integer",
            "description": "How many failing accessibility / SEO / best-practice audits to list per result.",
            "default": 10
          },
          "saveFullResults": {
            "title": "Save full PageSpeed JSON",
            "type": "boolean",
            "description": "Also store the complete raw PageSpeed Insights response for each result in the key-value store (large).",
            "default": false
          },
          "preflightCheck": {
            "title": "Pre-check URLs (skip non-HTML & 404)",
            "type": "boolean",
            "description": "Before calling PageSpeed, quickly fetch the URL headers: records the redirect chain and status, and skips (without charging) URLs that are PDFs/images/JSON, return 404/410, or have a non-existent domain.",
            "default": true
          },
          "locale": {
            "title": "Language of audit texts",
            "type": "string",
            "description": "Locale for Lighthouse audit titles, e.g. en, de, fr, es, pt-BR.",
            "default": "en"
          },
          "maxUrls": {
            "title": "Max URLs per run",
            "minimum": 1,
            "maximum": 100000,
            "type": "integer",
            "description": "Safety cap on unique URLs per run (before multiplying by devices).",
            "default": 500
          },
          "maxConcurrency": {
            "title": "Parallel audits",
            "minimum": 1,
            "maximum": 25,
            "type": "integer",
            "description": "How many PageSpeed audits run at the same time. Without an API key this is forced to 1.",
            "default": 5
          },
          "maxRequestsPerMinute": {
            "title": "Max PageSpeed requests per minute",
            "minimum": 1,
            "maximum": 400,
            "type": "integer",
            "description": "Client-side rate limit to stay inside Google's per-minute quota. 429 responses are retried with exponential backoff anyway.",
            "default": 60
          },
          "maxRetries": {
            "title": "Retries per URL",
            "minimum": 0,
            "maximum": 6,
            "type": "integer",
            "description": "Retries for transient failures (rate limits, timeouts, Google 5xx, flaky page loads). Permanent errors such as 404 or non-HTML are not retried.",
            "default": 3
          },
          "requestTimeoutSecs": {
            "title": "Timeout per audit (seconds)",
            "minimum": 30,
            "maximum": 300,
            "type": "integer",
            "description": "How long to wait for one PageSpeed Insights response. Heavy pages can take PSI 60-150 s; a timed-out URL is retried once.",
            "default": 180
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}