Speed Test

HTTP API · no client install

Agent quick reference

Use these same-origin endpoints to probe latency, measure download and upload throughput, and optionally save a shareable result.

Base URL window.location.origin

Recommended flow

  1. Identify server GET /api/v1/version
  2. Sample idle latency GET /api/v1/ping 10–20 times.
  3. Measure download stream GET /api/v1/download?duration=5 and count bytes client-side.
  4. Measure upload send application/octet-stream bodies to POST /api/v1/upload until your target duration ends.
  5. Optional share persist a summary with POST /api/v1/results.

Endpoints

GET

/health

Simple liveness check. Returns {"status":"ok"}.

GET

/api/v1/version

Server version and display name.

GET

/api/v1/ping

Latency probe plus observed client IP and IPv6 flag.

GET

/api/v1/download

Binary stream. Query: duration=1..300, chunk=65536..4194304.

POST

/api/v1/upload

Reads an octet-stream body and returns bytes, duration, and throughput.

POST

/api/v1/results

Save a result summary. Returns an 8-character result ID.

Browser snippet

async function ping(base = location.origin) {
  const start = performance.now();
  const res = await fetch(`${base}/api/v1/ping`, { cache: "no-store" });
  if (!res.ok) throw new Error(`ping failed: ${res.status}`);
  return { latencyMs: performance.now() - start, ...(await res.json()) };
}

async function downloadMbps(seconds = 5, base = location.origin) {
  const start = performance.now();
  const res = await fetch(
    `${base}/api/v1/download?duration=${seconds}&chunk=1048576`,
    { cache: "no-store" },
  );
  if (!res.ok || !res.body) throw new Error(`download failed: ${res.status}`);
  const reader = res.body.getReader();
  let bytes = 0;
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    bytes += value.byteLength;
  }
  const elapsed = (performance.now() - start) / 1000;
  return (bytes * 8) / elapsed / 1_000_000;
}

async function uploadOnce(size = 1048576, base = location.origin) {
  const payload = new Uint8Array(size);
  crypto.getRandomValues(payload);
  const res = await fetch(`${base}/api/v1/upload`, {
    method: "POST",
    headers: { "Content-Type": "application/octet-stream" },
    body: payload,
  });
  if (!res.ok) throw new Error(`upload failed: ${res.status}`);
  return res.json();
}

Agent notes

  • No authentication is required by default.
  • Rate limits and server capacity return JSON errors; respect Retry-After when present.
  • The canonical OpenAPI spec lives in api/openapi.yaml.