PageSpeedAudit Developers · Changelog
What changed, when, and what you have to do.
Every entry is dated to the day it reached production and says whether it was additive, a change to existing behaviour, or a fix toward what the contract already promised. A breaking change would ship under a new path version and be announced here first — none has. Dates are the days the repository's history records each surface reaching production.
- Version
- v1.0.0 ·
/api/v1 - Last contract change
- Findings document
pagespeed-audit/findings@1- OpenAPI
- /openapi/v1.json
Entries
-
Additive Run-completion webhooks
Register up to 5 https endpoints (dashboard session; POST /webhooks returns the signing secret once). Every run that finishes afterwards (Succeeded, Failed, TimedOut or Canceled) is POSTed as a run.finished event: {id, type, createdAt, data: {runId, auditId, runNumber, status, url, finishedAt, runUrl, reportUrl}}, reportUrl only when Succeeded. Each delivery carries PSA-Signature: t=<unix>,v1=<hex HMAC-SHA256 of "t.body" with the secret>, PSA-Event-Id and PSA-Event-Type. Any 2xx is success; failures are retried 7 more times over about two days. The run id is the event id, so a run is announced to an endpoint once. POST /webhooks/{id}/test sends a ping.
- Action required
- Optional: keep polling, or add an endpoint and stop polling. Verify the signature and refuse timestamps older than 5 minutes.
Changed Contact messages are filed under the account's address
POST /api/v1/contactfiles the message under the authenticated account's own address.emailis now optional; a different address is refused with400 email_mismatch. A refund or a restarted run the support team prepares for a message from another channel waits for the account owner to sign in and confirm it.- Action required
- Leave
emailout, or send the account's own address. - Operations
- POST
/api/v1/contact
Additive Four Verdicts and money ranges in the tag ledger
Each
tagsrow can carrydisposition(confirmed, false_positive, deprioritized or platform_blocked — Real, Wrong, Trivial or Locked),redirect(where the effort goes on a row that is not Real; only Real rows carryfix),platform(the store platform behind a Locked row) andmoney(a conversion range, and a monthly revenue range when the customer gave their revenue, for a measured LCP saving). A top-leveltag_totalssums the ledger, withby_disposition,noise_rateand the combinedmoney.- Action required
- None. The fields are optional.
- Operations
- GET
/api/v1/runs/{runId}/report
-
Additive Server-side replacements in the tag ledger
A
tagsrow can carry an optionalserver_sideobject when the tag's vendor accepts the same events from a server (for example the Meta Conversions API):replacement,removes_transfer_bytesandremoves_main_thread_ms(that tag's own cost, measured when the audit measured it),stays_in_browser(the tag manager that keeps its cost) andtradeoff. Server-side tagging is counted only for the browser tags it removes.- Action required
- None. The field is optional.
- Operations
- GET
/api/v1/runs/{runId}/report
-
Additive Tag ledger in the findings document
The findings document can carry an optional
tagsarray: one row per third-party tag with its lab cost on a mobile load (transfer_bytes,main_thread_ms,blocking_ms, andmeasured_gainswhen the audit measured the page with the tag blocked),category,owner,hosts,loaded_by,pre_consent, and averdict—keep,defer,offloadorremove-candidate— withreasonandfix. Consent, security and payment tags areprotectedand alwayskeep. Aremove-candidatecarriesevidence(duplicate,orphan,unused,owner) orowner_decides: true. No dollar figure is put on a tag. The schema at/docs/findings.schema.jsonand the generated types include it.- Action required
- None. The array is optional; documents without it are unchanged.
- Operations
- GET
/api/v1/runs/{runId}/report
Additive Audits of pages behind a login
POST /login-wallsays whether a page needs a sign-in.PUT /audits/{auditId}/accessstores access for an audit — a test account (method: "form"withloginUrl,username,password) or a session cookie (method: "cookie") — signs in once and returns the check'soutcomewith a plain-wordsmessageandfix.GETreads it back without the password or cookie,POST .../access/checksigns in again,DELETEremoves it. Runs of an audit with access measure the page signed in. A run that cannot sign in endsFailedwitherrorCode: signed_in_access_failed, does not count, and the access then showscheckedByRun: truewith the reason. Checks are limited to six per audit per ten minutes (429 too_many_checks).- Action required
- None. Audits without access run exactly as before.
-
Fixed A reseller's team member cannot order at the agency's rate
POST /ordersresolved wholesale pricing for any account belonging to an approved agency, team members included — so a member's own key was quoted, and could order, at the agency's rate onto the member's personal account, where the credits never appear in the agency's book. Ordering for an agency is the owner's, as it already was on the website: a member's key now gets403 not_agency_owner, on dry runs too, and nothing is created or charged.- Action required
- None for agency owners and ordinary accounts. A member's integration should use the owner's key.
- Operations
- POST
/api/v1/orders
-
Fixed A key created with run always holds read
Every run operation lives inside the account-reading family, so a key created with
["run"]alone was refused with403 insufficient_scopeby the one request it was made for.POST /apikeysnow grantsreadalongsiderun, and the response'sscopeslists what was actually granted.purchaseis unchanged and still implies nothing: a key that can only order remains a valid shape. Keys created earlier with run alone are unchanged — the dashboard marks them as unusable; create a new key and revoke the old one.- Action required
- None for keys created with read and run, which is the default. A script that created run-only keys gets working keys back and should read
scopesfrom the response. - Operations
- POST
/api/v1/apikeys
-
Additive Dry-run quotes on POST /orders
{ "dryRun": true }resolves the unit price, wholesale discount, daily ceilings and payment route exactly as a real order would, then creates nothing and charges nothing. It needs no Idempotency-Key and is the request the interactive reference sends by default. The operation's burst limit — 5 requests per minute per account — counts dry runs.- Action required
- None. Existing orders behave exactly as before.
- Operations
- POST
/api/v1/orders
Changed Ordering needs an API key; a signed-in session is refused
POST /orders,GET /ordersandGET /orders/{orderId}now answer403 api_key_requiredto a browser session. Scopes narrow keys, not people, so a signed-in session used to pass thepurchasecheck — which meant the request console on the reference page could place a real order for anyone who was merely signed in. The dashboard never ordered through these operations, so nothing that worked by design stops working.- Action required
- None for integrations, which always used a key. A script that reused a browser cookie to order must use a
purchase-scoped key.
Additive 402 problems carry the order they refused
402 authentication_requiredand402 card_declinednow carry the recorded order'sorderIdas a problem member, andauthentication_requiredalso carries thecheckoutUrlthat finishes the payment in a browser. Before, a caller had to listGET /ordersto find either.- Action required
- None. New members beside the existing
code,status,detail. - Operations
- POST
/api/v1/orders
Additive Every problem response links to its catalog entry
Problem responses now carry a
typeofhttps://pagespeedaudit.com/docs/errors#{code}and atitle(the status's reason phrase) beside the unchangedcode,statusanddetail. Ownership failures that answered an empty 404 now carry a problem body with codenot_found. The checkout, newsletter and contact refusals, which carried only a message, now carry codes too:unusable_email,checkout_unavailable,invalid_email,disposable_email,invalid_message— the message moved fromtitletodetailon the two checkout refusals.- Action required
- None for clients that branch on status or
code. A client that read the checkout refusal'stitleas the message readsdetail. - Operations
- GET
/api/v1/audits/{auditId}GET/api/v1/audits/{auditId}/runsPOST/api/v1/audits/{auditId}/runsGET/api/v1/runs/{runId}GET/api/v1/runs/{runId}/eventsGET/api/v1/runs/{runId}/reportGET/api/v1/orders/{orderId}GET/api/v1/target-finder/analyses/{analysisId}DELETE/api/v1/apikeys/{keyId}POST/api/v1/checkout/sessionsPOST/api/v1/newsletter/subscribePOST/api/v1/contact
Fixed Affiliate validation errors use the documented problem+json shape
Invalid dates, an unknown
groupByorstatus, and an unusable postback answered{ "error": "…" }, contrary to the contract. They are now problem+json with codesinvalid_date,invalid_group_by,invalid_status,invalid_postback_eventsandinvalid_postback_url; an account without an affiliate profile is403 not_an_affiliateon every affiliate operation. Status codes are unchanged.- Action required
- A client that parsed the old
errorfield — on the three operations with input validation, or on the 403 every affiliate operation returns — readsdetail(the same text) andcodeinstead.
Fixed API-key management is documented as dashboard-session only
The OpenAPI document declared the API-keys operations behind the
X-Api-Keyscheme, which they have always refused. They now declare theDashboardSessioncookie scheme, and an API key that calls them gets403 dashboard_session_requiredwith a body instead of an empty 403.409on key creation now carriesduplicate_key_nameorkey_limit_reached.- Action required
- None for correct integrations; keys are still created and revoked at /account/api-keys.
Changed Developer portal at /docs; the interactive reference moved to /docs/reference
/docs is now the guides, quickstart, schemas and error catalog; the generated reference lives at /docs/reference. /openapi/v1.json and /docs/openapi/v1.json are unchanged.
- Action required
- Update bookmarks to the reference; the OpenAPI document URL is the same.
-
Additive Saved-card charging on POST /orders
An account that saved a card under Billing and consented to off-session charging is charged immediately: the response is
202withpaymentRoute: chargeand the credits arrive when settlement lands. A card that needs the cardholder present is402 authentication_requiredwith a Checkout URL on the order; a declined card is402 card_declined. Accounts without a saved card still get201and a Checkout URL.- Action required
- None unless the account saves a card — then poll
GET /orders/{orderId}forcompletedinstead of sending a buyer to a URL.
Additive Ordering through the API: POST /orders, GET /orders, GET /orders/{orderId}
Orders audit credits on the account with a required
Idempotency-Key; an approved reseller's wholesale price is applied server-side. Guardrails: 5 orders per minute, per-account daily ceilings (429 daily_limit_reached), a platform backstop (503 ordering_paused) and a kill switch (503 ordering_disabled).- Action required
- None. Requires a key created with the
purchasescope.
Additive API key scopes: read, run, purchase
Keys carry scopes chosen at creation. A key created without a list gets
readandrun— the authority every existing key already had — so nothing that worked stopped working.purchaseis never implicit. Calling an operation without its scope is403 insufficient_scope, naming the scope. Starting a run and creating a Target Finder analysis needrun; everything else on audits and analyses needsread.- Action required
- None for existing keys. Create a
purchasekey only for an integration that orders.
Additive Lead attribution on affiliate stats
GET /affiliate/statscarries aleadsobject — unconfirmed, confirmed and converted leads, split by source. Lifetime figures; they ignorefrom/to.- Action required
- None.
- Operations
- GET
/api/v1/affiliate/stats
-
Additive Target Finder: POST /target-finder/analyses, GET …/{id}, GET …
Analyzes a site (robots → sitemap → homepage, URL templates, candidate probes, CrUX field-data checks) and recommends the page to audit, with alternates. Available while an audit's URL is still unlocked;
202with a pollable analysis.- Action required
- None.
Additive Recruit-an-affiliate reporting: GET /affiliate/recruiting
The recruit link, override percentage, recruit counts and override earnings by state, plus anonymous per-recruit rows.
- Action required
- None.
- Operations
- GET
/api/v1/affiliate/recruiting
-
Additive API v1 — first recorded in this repository
Checkout sessions, audits and runs (start, poll, timeline, report download as findings JSON or self-contained HTML), dashboard-session API-key management, the affiliate API (profile, stats, referrals, payouts, postback), newsletter subscription and contact intake. OpenAPI 3.1 at /openapi/v1.json; problem+json errors with a stable
code. This is the earliest date the repository's history records for the surface.- Action required
- —
Versioning and deprecation
The rules a change has to follow.
Additive on v1
New operations, new optional fields, new error codes and new example values may appear on /api/v1 at any time. Existing fields are never removed, renamed or retyped; existing status codes and code values keep their meaning.
Breaking means a new path
Anything a correct v1 client could not absorb ships as /api/v2. v1 keeps working through a published sunset window; the window and the migration notes appear on this page before the new version does.
The findings document
The schema id pagespeed-audit/findings@1 covers additive change the same way. A change that breaks a consumer of the document becomes a new schema id, announced here, with both formats available for a window.
Fixes toward the contract
When the implementation is found to differ from what the reference already documents — a wrong error shape, a missing security scheme — it is fixed and recorded as Fixed, with the old and new behaviour stated, so a client that depended on the mistake can see what to do.
Operational status
Live, from the same checks the deploy gate reads.
Checked · refreshed at most once a minute
- API and database Operational
- Audit processing (worker heartbeats) Operational
Machine-readable: /healthz/ready answers 200 when the API can serve and 503 when it cannot. No incident history is published yet; when one is, it will live here.