Vanilla JavaScript is the simplest way to integrate the SpeedOf.Me speed test API. No frameworks or build tools required.
<script src="https://speedof.me/api/api.js"></script>
// Required configuration
SomApi.account = "YOUR_API_KEY";
SomApi.domainName = "your-domain.com";
// Callbacks
SomApi.onTestCompleted = function(result) {
console.log('Download:', result.download, 'Mbps');
console.log('Upload:', result.upload, 'Mbps');
console.log('Latency:', result.latency, 'ms');
};
SomApi.onError = function(error) {
console.error('Error:', error.code, error.message);
};
// Start test
SomApi.startTest();
The API validates the URL of the page that embeds it against the domain registered to
your key, so the page must be served over http or https from that domain.
localhost and localhost:8080 work for development once
registered. A page with no host (a file:// path, an app bundle, an asset or
an HTML string) always fails with error 1002, so opening either complete example below
straight from disk will not run a test.
// Test behavior
SomApi.config.sustainTime = 6; // 1-8 seconds (higher = more accurate)
SomApi.config.testServerEnabled = true; // Include server location
SomApi.config.userInfoEnabled = true; // Include IP/hostname
SomApi.config.latencyTestEnabled = true; // Run latency test
SomApi.config.uploadTestEnabled = true; // Run upload test
// Progress updates
SomApi.config.progress.enabled = true; // Enable progress callbacks
SomApi.config.progress.verbose = true; // Adds currentSpeed/maxSpeed to download and upload events (default false)
SomApi.onTestCompleted = function(result) {
result.download; // Download speed in Mbps
result.upload; // Upload speed in Mbps ('' if uploadTestEnabled is false)
result.maxDownload; // Peak download speed in Mbps
result.maxUpload; // Peak upload speed in Mbps ('' if uploadTestEnabled is false)
result.latency; // Latency in ms ('' if latencyTestEnabled is false)
result.jitter; // Jitter in ms ('' if latencyTestEnabled is false)
result.testServer; // Server location like "Los Angeles 3" ('' if testServerEnabled is false)
result.ip_address; // Client IP address ('0.0.0.0' if userInfoEnabled is false)
result.hostname; // Client hostname ('' if userInfoEnabled is false)
result.userAgent; // Browser user agent string
result.testDate; // Test timestamp, ISO 8601
};
SomApi.onProgress = function(progress) {
progress.type; // 'latency', 'download' or 'upload'
progress.percentDone; // 0-100
progress.pass; // Current test pass ('' during the latency phase)
progress.currentSpeed; // Current speed in Mbps, verbose mode only: '' unless
// config.progress.verbose is true, and always ''
// on latency events
progress.maxSpeed; // Peak speed in Mbps, same rule as currentSpeed
progress.latency; // Latency in ms, only on the final latency event
// (type 'latency' at percentDone 100), '' otherwise
progress.jitter; // Jitter in ms, same rule as latency
};
SomApi.onError = function(error) {
error.code; // see the table below
error.message; // Human-readable message
};
1001 Invalid Account: API key not found or inactive1002 Domain Mismatch: the page domain does not match your registered domain2001 Test Error2002 Invalid server response2003 Request timeout2004 Test timeout: the test did not finish within config.testTimeout2005 Speed test could not start (status N)2006 Speed test engine did not loadCode 2006, and 2005 with status 0, mean the request never left the
browser, which is almost always an ad blocker, a content blocker or a privacy extension
on the visitor's device. The one thing to check on your own side is a frame-src
Content-Security-Policy: it must allow https://speedof.me, or the engine
iframe can never load. A 2005 carrying any other status is a real HTTP response, so treat
it as a server-side failure.
Minimal implementation with start button and results display:
<!DOCTYPE html>
<html>
<head>
<title>Speed Test</title>
<script src="https://speedof.me/api/api.js"></script>
<style>
body { font-family: sans-serif; max-width: 400px; margin: 50px auto; text-align: center; }
button { padding: 15px 40px; font-size: 18px; cursor: pointer; }
#results { margin-top: 20px; font-size: 18px; }
</style>
</head>
<body>
<h1>Speed Test</h1>
<button onclick="startTest()">Start Test</button>
<div id="results"></div>
<script>
SomApi.account = "YOUR_API_KEY";
SomApi.domainName = "your-domain.com";
SomApi.onTestCompleted = function(r) {
document.getElementById('results').innerHTML =
'Download: ' + r.download + ' Mbps<br>' +
'Upload: ' + r.upload + ' Mbps<br>' +
'Latency: ' + r.latency + ' ms';
};
SomApi.onError = function(e) {
document.getElementById('results').innerHTML = 'Error: ' + e.message;
};
function startTest() {
document.getElementById('results').innerHTML = 'Testing...';
SomApi.startTest();
}
</script>
</body>
</html>
Full-featured implementation with Bootstrap 5, settings panel, progress bar, and styled results. Copy and paste this entire file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Speed Test - Advanced</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css" rel="stylesheet">
<!-- SpeedOf.Me API -->
<script src="https://speedof.me/api/api.js"></script>
<style>
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh;
color: #fff;
}
.card {
background: rgba(255, 255, 255, 0.95);
border: none;
border-radius: 1rem;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
.settings-card {
background: #f8f9fa;
border-radius: 0.75rem;
}
.result-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.speed-value {
font-size: 2.5rem;
font-weight: 700;
line-height: 1;
}
.speed-unit {
font-size: 0.9rem;
opacity: 0.8;
}
.btn-start {
background: linear-gradient(135deg, #00d4ff 0%, #090979 100%);
border: none;
padding: 0.875rem 2.5rem;
font-size: 1.1rem;
font-weight: 600;
border-radius: 50px;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn-start:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 212, 255, 0.4);
}
.btn-start:disabled { opacity: 0.6; }
.progress-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 0.75rem;
color: #fff;
}
.progress-bar-custom {
height: 8px;
border-radius: 4px;
background: rgba(255,255,255,0.2);
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: #fff;
border-radius: 4px;
transition: width 0.3s ease;
}
.form-check-input:checked {
background-color: #667eea;
border-color: #667eea;
}
.live-speed {
font-size: 3rem;
font-weight: 700;
}
</style>
</head>
<body>
<div class="container py-5">
<!-- Header -->
<div class="text-center mb-4">
<h1 class="display-5 fw-bold mb-2">
<i class="bi bi-speedometer2 me-2"></i>Speed Test
</h1>
</div>
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card">
<div class="card-body p-4">
<div class="row">
<!-- Settings Column -->
<div class="col-lg-4 mb-4 mb-lg-0">
<div class="settings-card p-3">
<h5 class="mb-3">
<i class="bi bi-sliders me-2"></i>Settings
</h5>
<!-- Sustain Time -->
<div class="mb-4">
<label class="form-label d-flex justify-content-between">
<span>Sustain Time</span>
<span class="badge bg-primary" id="sustainTimeValue">6</span>
</label>
<div class="d-flex align-items-center">
<small class="text-muted me-2">Fast</small>
<input type="range" class="form-range flex-grow-1" id="sustainTime"
min="1" max="8" value="6" oninput="updateSustainTime()">
<small class="text-muted ms-2">Accurate</small>
</div>
</div>
<!-- Options -->
<div class="mb-3">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="latencyTest" checked>
<label class="form-check-label" for="latencyTest">
<i class="bi bi-clock me-1"></i>Latency Test
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="uploadTest" checked>
<label class="form-check-label" for="uploadTest">
<i class="bi bi-arrow-up me-1"></i>Upload Test
</label>
</div>
</div>
<!-- Start Button -->
<div class="d-grid mt-4">
<button id="btnStart" class="btn btn-primary btn-start" onclick="startTest()">
<i class="bi bi-play-fill me-2"></i>Start Test
</button>
</div>
</div>
</div>
<!-- Results Column -->
<div class="col-lg-8">
<!-- Initial State -->
<div id="initialState" class="text-center py-5">
<i class="bi bi-speedometer2 text-muted" style="font-size: 4rem;"></i>
<h4 class="text-muted mt-3">Ready to Test</h4>
<p class="text-muted">Click Start Test to begin</p>
</div>
<!-- Progress State -->
<div id="progressState" style="display: none;">
<div class="progress-section p-4 mb-4">
<div class="text-center mb-3">
<div class="text-uppercase small opacity-75" id="progressType">Initializing</div>
<div class="live-speed" id="liveSpeed">--</div>
<div class="small opacity-75">Mbps</div>
</div>
<div class="progress-bar-custom">
<div class="progress-bar-fill" id="progressBar" style="width: 0%"></div>
</div>
<div class="text-center mt-2 small opacity-75" id="progressPercent">0%</div>
</div>
</div>
<!-- Results State -->
<div id="resultsState" style="display: none;">
<div class="row g-3 mb-4">
<div class="col-6">
<div class="card result-card h-100">
<div class="card-body text-center py-4">
<i class="bi bi-arrow-down-circle"></i>
<div class="speed-value mt-2" id="downloadSpeed">--</div>
<div class="speed-unit">Mbps Download</div>
</div>
</div>
</div>
<div class="col-6">
<div class="card result-card h-100">
<div class="card-body text-center py-4">
<i class="bi bi-arrow-up-circle"></i>
<div class="speed-value mt-2" id="uploadSpeed">--</div>
<div class="speed-unit">Mbps Upload</div>
</div>
</div>
</div>
<div class="col-6">
<div class="card bg-light h-100">
<div class="card-body text-center py-3">
<i class="bi bi-clock text-primary"></i>
<div class="fs-3 fw-bold text-dark mt-2" id="latency">--</div>
<div class="text-muted small">ms Latency</div>
</div>
</div>
</div>
<div class="col-6">
<div class="card bg-light h-100">
<div class="card-body text-center py-3">
<i class="bi bi-activity text-primary"></i>
<div class="fs-3 fw-bold text-dark mt-2" id="jitter">--</div>
<div class="text-muted small">ms Jitter</div>
</div>
</div>
</div>
</div>
<!-- Additional Info -->
<div class="p-3 bg-light rounded">
<div class="row text-center g-2">
<div class="col-md-4">
<small class="text-muted d-block">Test Server</small>
<span class="fw-semibold text-dark small" id="testServer">--</span>
</div>
<div class="col-md-4">
<small class="text-muted d-block">Your IP</small>
<span class="fw-semibold text-dark small" id="ipAddress">--</span>
</div>
<div class="col-md-4">
<small class="text-muted d-block">Hostname</small>
<span class="fw-semibold text-dark small" id="hostname">--</span>
</div>
</div>
</div>
</div>
<!-- Error State -->
<div id="errorState" style="display: none;">
<div class="alert alert-danger text-center">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<span id="errorMessage">An error occurred</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// ========================================
// SpeedOf.Me API Configuration
// ========================================
SomApi.account = "YOUR_API_KEY"; // Replace with your API key
SomApi.domainName = "your-domain.com"; // Replace with your domain
// Callbacks
SomApi.onTestCompleted = onTestCompleted;
SomApi.onError = onError;
SomApi.onProgress = onProgress;
// Enable progress
SomApi.config.progress.enabled = true;
SomApi.config.progress.verbose = true;
// ========================================
// UI Functions
// ========================================
function updateSustainTime() {
var value = document.getElementById('sustainTime').value;
document.getElementById('sustainTimeValue').textContent = value;
}
function startTest() {
// Apply settings from UI
SomApi.config.sustainTime = Number(document.getElementById('sustainTime').value);
SomApi.config.latencyTestEnabled = document.getElementById('latencyTest').checked;
SomApi.config.uploadTestEnabled = document.getElementById('uploadTest').checked;
// Update UI
document.getElementById('initialState').style.display = 'none';
document.getElementById('progressState').style.display = 'none';
document.getElementById('resultsState').style.display = 'none';
document.getElementById('errorState').style.display = 'none';
document.getElementById('btnStart').disabled = true;
document.getElementById('btnStart').innerHTML =
'<span class="spinner-border spinner-border-sm me-2"></span>Testing...';
SomApi.startTest();
}
function onProgress(progress) {
var type = (progress.type || '').toLowerCase();
// Show progress during download/upload
if (type.includes('download') || type.includes('upload')) {
document.getElementById('progressState').style.display = 'block';
document.getElementById('progressType').textContent = progress.type;
document.getElementById('liveSpeed').textContent = progress.currentSpeed || '--';
document.getElementById('progressBar').style.width = progress.percentDone + '%';
document.getElementById('progressPercent').textContent = progress.percentDone + '%';
}
}
function onTestCompleted(result) {
document.getElementById('progressState').style.display = 'none';
document.getElementById('resultsState').style.display = 'block';
document.getElementById('downloadSpeed').textContent = result.download || '--';
document.getElementById('uploadSpeed').textContent = result.upload || '--';
document.getElementById('latency').textContent = result.latency || '--';
document.getElementById('jitter').textContent = result.jitter || '--';
document.getElementById('testServer').textContent = result.testServer || '--';
document.getElementById('ipAddress').textContent = result.ip_address || '--';
document.getElementById('hostname').textContent = result.hostname || '--';
resetButton();
}
function onError(error) {
document.getElementById('progressState').style.display = 'none';
document.getElementById('errorState').style.display = 'block';
document.getElementById('errorMessage').textContent =
'Error ' + error.code + ': ' + error.message;
resetButton();
}
function resetButton() {
document.getElementById('btnStart').disabled = false;
document.getElementById('btnStart').innerHTML =
'<i class="bi bi-arrow-clockwise me-2"></i>Test Again';
}
</script>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>