Page speed (one lab measurement)
POST /api/page-speedLoad one public URL once in headless Chromium and report what that load measured: navigation timings (TTFB, DOMContentLoaded, load), Web Vitals read from the page's own performance entries (LCP, FCP, CLS, total blocking time from long tasks), total transfer bytes, request count, the 5 heaviest resources, render-blocking scripts and styles, and console errors. Send POST /api/page-speed with the required field url and pay $0.01 per call over x402 or MPP (there is no free tier). It returns a JSON object with url, finalUrl, status, device, viewport and 12 more.
Mobile or desktop viewport. This is a single measurement from one server location under shared load, with no throttling: it is not field data from real users and not a Lighthouse score, and the answer says so in its measurement field. Deterministic code, no model. Body: {"url":"https://…","device":"mobile"|"desktop"?}.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
url | string | yes | Public http(s) URL to load and measure Also accepted as link, uri, href, page. |
device | string (one of: mobile, desktop) | no | Viewport profile: mobile (412x823, touch) or desktop (1350x940). Default mobile |
Example request
curl -i -X POST https://agent402.tools/api/page-speed \
-H "Content-Type: application/json" \
-d '{"url":"https://agent402.tools","device":"mobile"}'
Without payment this returns HTTP 402 Payment Required with the exact price for page-speed; any x402 v2 or MPP client pays it and retries.
Example response
{
"url": "https://agent402.tools",
"finalUrl": "https://agent402.tools/",
"status": 200,
"device": "mobile",
"viewport": {
"width": 412,
"height": 823
},
"networkIdle": true,
"timings": {
"ttfbMs": 182,
"domContentLoadedMs": 401,
"loadMs": 655
},
"webVitals": {
"lcpMs": 512,
"lcpElement": "h1",
"fcpMs": 430,
"cls": 0.0012,
"totalBlockingTimeMs": 0,
"longTaskCount": 0
},
"transfer": {
"totalBytes": 98123,
"requestCount": 9,
"failedRequests": 0
},
"heaviestResources": [
{
"url": "https://agent402.tools/",
"type": "document",
"bytes": 41210
}
],
"heaviestResourcesPartial": {
"returned": 1,
"matched": 9,
"complete": false,
"truncated": true
},
"renderBlocking": [],
"renderBlockingPartial": {
"returned": 0,
"matched": 0,
"complete": true,
"truncated": false
},
"consoleErrors": [],
"consoleErrorsPartial": {
"returned": 0,
"matched": 0,
"complete": true,
"truncated": false
},
"measurement": {
"kind": "lab-single-run",
"runs": 1,
"source": "one load in a headless Chromium from one server location, on shared infrastructure, with no network or CPU throttling",
"fieldData": false,
"lighthouseScore": false,
"note": "A single measurement from one server location under shared load. It is not field data from real users and not a Lighthouse score; repeat the call and compare runs before drawing conclusions."
},
"untrustedContent": true
}
| Field | Type | Always present | In the example |
|---|---|---|---|
url | string | yes | https://agent402.tools |
finalUrl | string | yes | https://agent402.tools/ |
status | number | yes | 200 |
device | string | yes | mobile |
viewport | object | yes | 2 fields: width, height |
networkIdle | boolean | yes | true |
timings | object | yes | 3 fields: ttfbMs, domContentLoadedMs, loadMs |
webVitals | object | yes | 6 fields: lcpMs, lcpElement, fcpMs, cls, totalBlockingTimeMs, longTaskCount |
transfer | object | yes | 3 fields: totalBytes, requestCount, failedRequests |
heaviestResources | array of objects | yes | 1 item in the example |
heaviestResourcesPartial | object | yes | 4 fields: returned, matched, complete, truncated |
renderBlocking | array | yes | 0 items in the example |
renderBlockingPartial | object | yes | 4 fields: returned, matched, complete, truncated |
consoleErrors | array | yes | 0 items in the example |
consoleErrorsPartial | object | yes | 4 fields: returned, matched, complete, truncated |
measurement | object | yes | 6 fields: kind, runs, source, fieldData, lighthouseScore, note |
untrustedContent | boolean | yes | true |
From an MCP client
catalog.call {
"slug": "page-speed",
"params": {
"url": "https://agent402.tools",
"device": "mobile"
}
}
The hosted connector at https://agent402.tools/mcp needs a payment for page-speed; the stdio package pays it from a wallet or from an AGENT402_CREDITS_KEY issued earlier. Local install: npx -y agent402-mcp.
Errors and behavior
urlis required. An input the tool rejects returns an HTTP 4xx whose body carrieserror,tool,expected,requiredandexample, so the caller can correct it.- A paid call that ends in any status of 400 or above is not charged over x402, MPP or a prepaid credits key: settlement is cancelled when the tool fails. The exception is a Tempo push credential, a transfer the buyer sent before the call: it settles before the tool runs, so if the tool then fails the payment is recorded as a refund owed to the paying wallet.
- Wallet-only: this tool reaches the network or stored state, so it has no proof-of-work tier. A prepaid card-credits key issued earlier (
Authorization: Bearer a402_...) also pays it. - A
GETorHEADto /api/page-speed returns the same 402 quote, so the price can be read without a body. - An
Idempotency-Keyheader makes a retried paid call replay the first 200 instead of charging again (an answer larger than 1 MB is not replayed).
Paid call (JavaScript agent)
import { wrapFetchWithPayment } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const client = new x402Client();
client.setSpendControls?.({ maxAmountPerPayment: "$0.01" }); // refuse any quote above this tool's price
registerExactEvmScheme(client, { signer: privateKeyToAccount(KEY) });
const payFetch = wrapFetchWithPayment(fetch, client);
const res = await payFetch("https://agent402.tools/api/page-speed", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
"url": "https://agent402.tools",
"device": "mobile"
}),
});
Related tools
HTML frames (render at times)
POST /api/html-framesRender a self-contained HTML document in headless Chromium with all network access refused, call its window.renderAt(t).
Browser render
POST /api/renderRender a page in a real headless Chromium browser (JavaScript executed), then extract the main content as clean.
Web answer
GET /api/answerAI-generated answer to a natural-language question, grounded in live web search results with source citations.
Wayback Machine snapshot
POST /api/archive-snapshotLook up a URL in the Internet Archive's Wayback Machine: returns the archived snapshot closest to an optional timestamp.
Audio convert (to MP3)
POST /api/audio-convertExtract/convert the audio track of any media URL (mp4, mov, wav, m4a, ogg…) to MP3 - the "mp4 to mp3" conversion.