API for agents and LLMs

This is not a separate API. It is the exact surface the browser calls when you run the speed test on this site, documented so a script, an agent or an LLM tool can call it too. No key, no sign-up, no CORS.

There is no separate "public API" here: these are the same four endpoints the browser on this site calls to run a speed test, with no API key, no account, and (see below) no CORS header.

Endpoints

GET/api/speed/pingUnmetered

A minimal round-trip probe.

The t field in the response is diagnostic only. Workers freeze Date.now() between I/O operations, so it is not a trustworthy clock for measuring latency. Time the request yourself.

GET/api/speed/metaUnmetered

Reflects what Cloudflare sees about the caller: IP, ISP, ASN, city, country, and the edge (colo) that answered.

Nothing here is stored. It is echoed back to the caller and nowhere else.

GET/api/speed/down120 requests / 60 s / IP

Streams bytes (query parameter, default 1 MB, capped at 100 MB) of random data, for download-throughput measurement.

The response is no-store, no-transform. Nothing compresses or caches it, either of which would inflate a measured speed.

POST/api/speed/up120 requests / 60 s / IP

Accepts a request body of up to 100 MB and reports how many bytes arrived and how long that took, for upload-throughput measurement.

A declared Content-Length above 100 MB is rejected with 413 before the body is read.

The rate limit, as arithmetic

/api/speed/down and /api/speed/up share one limit of 120 requests per IP per 60-second window, and the window resets on its own. On a gigabit line a default speed test makes about 40 of those requests (four download and four upload streams, each re-requesting until its phase ends; slower lines make fewer), so that is roughly 3 full tests per window. Past the limit you get 429 with a Retry-After header. The count is kept per Cloudflare location, so treat it as approximate. /api/speed/ping and /api/speed/meta are unmetered: their payloads are trivial, and metering them would add a check to the exact path they exist to measure.

The largest single request either endpoint will serve or accept is 100 MB.

No CORS, on purpose

These endpoints send no Access-Control-Allow-Origin header, and none of them sit behind the same-origin check this site uses on its account and billing routes. That combination is deliberate: a server-side caller, curl, an agent, an LLM tool, already works today, while a browser running cross-origin JavaScript against these endpoints is already blocked by the browser itself. Adding * CORS would let any website spend this site’s bandwidth from its own visitors’ browsers; documenting the current shape costs nothing and changes nothing.

A worked example: curl

Download 5 MB and time it, the standard shape of a manual throughput check.

curl -o /dev/null -w "%{time_total}s for %{size_download} bytes\n" \
  "https://mysygnal.net/api/speed/down?bytes=5000000"

A worked example: an agent or LLM tool

The same measurement, timed by the caller rather than trusted from the response. See the note on /api/speed/ping above.

const t0 = performance.now();
const res = await fetch("https://mysygnal.net/api/speed/down?bytes=5000000");
await res.arrayBuffer();
const seconds = (performance.now() - t0) / 1000;
const mbps = (5_000_000 * 8) / seconds / 1_000_000;

Everything else this site offers a caller

llms.txt indexes the tools, the guides and every router page on this site in one plain-text file, meant to be read by a model rather than a browser.