SpeedOf.Me API - Vanilla JavaScript

Vanilla JavaScript is the simplest way to integrate the SpeedOf.Me speed test API. No frameworks or build tools required.

Quick Start

1. Include the API Script

<script src="https://speedof.me/api/api.js"></script>

2. Configure and Start

// 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();

Where the Page Must Be Hosted

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.

Configuration Options

// 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)

Callbacks Reference

onTestCompleted(result)

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
};

onProgress(progress)

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
};

onError(error)

SomApi.onError = function(error) {
    error.code;    // see the table below
    error.message; // Human-readable message
};

Code 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.

Complete Example - Basic

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>

Complete Example - Advanced with UI

Full-featured implementation with Bootstrap 5, settings panel, progress bar, and styled results. Copy and paste this entire file:

Tip: Replace YOUR_API_KEY and your-domain.com with your credentials from the API Portal.
<!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>

Links