TTFB measurement script examples
Time to First Byte (TTFB) is the single most revealing metric when you need to understand how fast an API endpoint actually responds under real-world conditions. For teams that rely on LLM APIs, whether through OpenAI directly or via aggregators like OpenRouter, measuring TTFB from multiple regions is the difference between guessing and knowing. This resource collects battle-tested script examples in cURL, Python, Node.js, and Bash so you can start capturing TTFB data in minutes, not days.

Time to First Byte (TTFB) is the single most revealing metric when you need to understand how fast an API endpoint actually responds under real-world conditions. For teams that rely on LLM APIs, whether through OpenAI directly or via aggregators like OpenRouter, measuring TTFB from multiple regions is the difference between guessing and knowing. This resource collects battle-tested script examples in cURL, Python, Node.js, and Bash so you can start capturing TTFB data in minutes, not days.
TL;DR
- TTFB measures the elapsed time from sending a request until the first response byte arrives, it exposes DNS, TLS, server processing, and network latency in a single number.
- You can measure TTFB with nothing more than
curl -wand a terminal, but Python and Node.js scripts give you programmatic control for scheduled probes. - Running measurements from a single location hides regional variance; always probe from at least three geographically distinct points.
- Automating TTFB collection into a cron job or CI pipeline turns a one-off check into continuous observability.
- Observinio already runs daily TTFB probes from 21 regions, you can compare your DIY numbers against its status page baselines.
stream=True in Python, the response callback in Node.js) when measuring TTFB, otherwise your timing captures the full response transfer rather than the moment the first byte arrives.Why TTFB matters for LLM API monitoring
When a user sends a prompt to your chat feature, the perceived speed is dominated by two things: how quickly the first token appears on screen (Time to First Token, or TTFT) and how fast subsequent tokens stream in. TTFB is the network-level precursor to TTFT. If TTFB is high, TTFT cannot be low, the bytes simply have not started arriving yet.
Traditional web performance tooling treats TTFB as a single aggregate number. That works for a marketing page served from a CDN, but LLM API calls are different. The server-side processing time for a completion request can swing from 200 ms to several seconds depending on model load, prompt length, and provider-side queuing. Isolating the network component from the inference component requires you to measure TTFB at the transport layer, before your application even begins parsing the response body.
What TTFB actually includes
A TTFB measurement captures the cumulative time of several phases:
- DNS lookup, resolving the API hostname to an IP address.
- TCP connect, establishing the transport connection.
- TLS handshake, negotiating encryption (virtually all API endpoints use HTTPS).
- Request transfer, sending your HTTP request headers and body to the server.
- Server processing, the provider receives the request, routes it internally, and begins generating a response.
- First byte back, the moment the first byte of the HTTP response reaches your network interface.
"The time, in seconds, it took from the start until the first byte was just about to be transferred.">, GitHub
Understanding this breakdown is critical because a high TTFB might be caused by slow DNS (fixable on your side), a distant server (fixable by choosing a closer region), or genuine provider slowness (not fixable on your side, but detectable with the right monitoring).
Script examples: measuring TTFB in practice
Below are four ready-to-use scripts. Each one targets an HTTPS endpoint and extracts TTFB. Replace the example URL with your actual LLM API endpoint (e.g., https://openrouter.ai/api/v1/chat/completions or https://api.openai.com/v1/chat/completions).
Example 1: cURL one-liner
The simplest approach uses cURL's built-in timing variables. No dependencies beyond a standard Unix shell.
curl -o /dev/null -s -w "dns: %{time_namelookup}s\ntcp: %{time_connect}s\ntls: %{time_appconnect}s\nttfb: %{time_starttransfer}s\ntotal: %{time_total}s\n" \
-X POST https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}'
The key variable is time_starttransfer, that is your TTFB. The other variables let you decompose the total time into DNS, TCP, and TLS phases so you can pinpoint where latency hides.
Example 2: Bash loop for repeated sampling
A single measurement is noisy. This script runs 10 iterations and writes results to a CSV file for later analysis.
#!/usr/bin/env bash
set -euo pipefail
ENDPOINT="https://openrouter.ai/api/v1/chat/completions"
OUTPUT="ttfb_results.csv"
ITERATIONS=10
echo "iteration,dns,tcp,tls,ttfb,total" > "$OUTPUT"
for i in $(seq 1 "$ITERATIONS"); do
result=$(curl -o /dev/null -s -w "%{time_namelookup},%{time_connect},%{time_appconnect},%{time_starttransfer},%{time_total}" \
-X POST "$ENDPOINT" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}')
echo "$i,$result" >> "$OUTPUT"
sleep 2
done
echo "Done. Results in $OUTPUT"
The two-second sleep between iterations avoids rate-limit issues and gives the provider time to return to a neutral state between requests. You can import the resulting CSV into any spreadsheet or data tool to compute p50, p95, and p99 TTFB values.
Example 3: Python with requests and timing hooks
Python gives you programmatic flexibility, you can push results to a database, trigger alerts, or integrate with existing monitoring pipelines.
import time
import requests
API_URL = "https://openrouter.ai/api/v1/chat/completions"
HEADERS = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
PAYLOAD = {
"model": "openai/gpt-4o-mini",
"messages": [{"role": "user", "content": "ping"}],
}
def measure_ttfb(url: str, headers: dict, json_body: dict) -> dict:
"""Send a POST request and return timing breakdown in seconds."""
start = time.monotonic()
response = requests.post(url, headers=headers, json=json_body, stream=True)
ttfb = time.monotonic() - start # first chunk received
# consume the rest of the body
body = response.content
total = time.monotonic() - start
return {
"ttfb_s": round(ttfb, 4),
"total_s": round(total, 4),
"status": response.status_code,
}
if __name__ == "__main__":
for i in range(5):
result = measure_ttfb(API_URL, HEADERS, PAYLOAD)
print(f"Run {i+1}: TTFB={result['ttfb_s']}s Total={result['total_s']}s Status={result['status']}")
time.sleep(2)
Setting stream=True is essential. Without it, requests waits for the entire response body before returning, and your timing captures total transfer time rather than true TTFB.
Example 4: Node.js with built-in http timings
For JavaScript-heavy teams, Node.js provides low-level socket events that map directly to TTFB phases.
const https = require("https");
const options = {
hostname: "openrouter.ai",
path: "/api/v1/chat/completions",
method: "POST",
headers: {
Authorization: "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
};
const body = JSON.stringify({
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: "ping" }],
});
function measureTTFB() {
return new Promise((resolve, reject) => {
const start = process.hrtime.bigint();
const req = https.request(options, (res) => {
const ttfb = Number(process.hrtime.bigint() - start) / 1e6; // ms
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
const total = Number(process.hrtime.bigint() - start) / 1e6;
resolve({ ttfb_ms: ttfb.toFixed(1), total_ms: total.toFixed(1), status: res.statusCode });
});
});
req.on("error", reject);
req.write(body);
req.end();
});
}
(async () => {
for (let i = 1; i <= 5; i++) {
const result = await measureTTFB();
console.log(Run ${i}: TTFB=${result.ttfb_ms}ms Total=${result.total_ms}ms Status=${result.status});
await new Promise((r) => setTimeout(r, 2000));
}
})();
The callback on https.request fires when response headers arrive, that moment is your TTFB. Using process.hrtime.bigint() gives nanosecond precision, which avoids the jitter problems of Date.now().
Key takeaway: Always use streaming mode (stream=True in Python, the response callback in Node.js) when measuring TTFB, otherwise your timing captures the full response transfer rather than the moment the first byte arrives.
Measuring from multiple regions
A TTFB measurement from your laptop in Warsaw tells you nothing about what your users in São Paulo or Tokyo experience. LLM API providers typically host inference in a small number of data center regions, and the physical distance between the caller and the inference server adds real, measurable latency.
DIY multi-region approach
To probe from multiple locations yourself, you have several options:
- Cloud VMs, spin up small instances in AWS, GCP, or Azure regions. Deploy one of the scripts above via cloud-init or a simple systemd timer. Cost: a few dollars per month per region for a
t4g.nanoor equivalent. - GitHub Actions matrix, use a matrix strategy to run your cURL script on GitHub-hosted runners. Runners are located in Azure US regions, so geographic diversity is limited, but it is free and requires zero infrastructure.
- Serverless functions, deploy a Lambda or Cloud Function in each target region. Trigger them on a schedule (e.g., every 15 minutes) and write results to a shared datastore.
Typical TTFB baselines by region
What to watch for in multi-region data
- Consistent baseline per region. Establish a normal TTFB range for each location. A 150 ms TTFB from Frankfurt and a 400 ms TTFB from Sydney might both be perfectly healthy.
- Sudden spikes in one region. If Frankfurt jumps from 150 ms to 900 ms while other regions stay flat, the issue is likely regional, a network path change, a provider edge node problem, or a regional capacity constraint.
- Global degradation. If all regions spike simultaneously, the provider's core inference infrastructure is likely under stress. This is the pattern that matters most for incident response.
Automating collection and alerting
Running scripts manually is useful for debugging, but production monitoring requires automation. Here is a practical checklist for turning the scripts above into a lightweight TTFB monitoring pipeline:
Automation checklist
Your progress is saved automatically in your browser.
When DIY is not enough
Building and maintaining a multi-region probe fleet is real operational work. You need to keep VMs patched, handle API key rotation, manage storage costs, and build dashboards. For many teams, the engineering hours spent on probe infrastructure exceed the cost of a dedicated monitoring service. This is especially true when you need coverage across 10+ regions or want sub-hour probe frequency with historical trend analysis.
| Method | Precision | Multi-region | Automation | Setup effort |
|---|---|---|---|---|
| cURL one-liner | Millisecond | Manual | Via cron | Minimal |
| Bash loop script | Millisecond | Manual | Via cron | Low |
| Python requests | Microsecond | Cloud VMs / Lambda | Native scheduling | Medium |
| Node.js https | Nanosecond | Cloud VMs / Lambda | Native scheduling | Medium |
| Observinio (managed) | Millisecond | 21 regions built-in | Fully automated | None |
Frequently Asked Questions
time_starttransfer captures the first byte regardless of transfer encoding, and the Python stream=True approach returns control as soon as headers and the first chunk arrive.Start measuring, then automate
The scripts in this resource get you from zero to TTFB data in under five minutes. Start with the cURL one-liner to validate your endpoint, graduate to the Python or Node.js version for scheduled probes, and layer on multi-region coverage as your needs grow. If you would rather skip the infrastructure work entirely, Observinio already runs daily probes against OpenRouter and OpenAI endpoints from 21 global regions, compares results against established baselines, and sends email alerts when degradation is detected. You can use the status page as an independent reference point alongside your own measurements, or let it handle the heavy lifting while you focus on building your product.
Additional Resources
- jaygooby/ttfb.sh: Measures time-to-first-byte in seconds, for ... - Measures time-to-first-byte for single or multiple URLs. Can show you quickest, slowest & median TTFB values plus optionally log all response headers. Examples ...
- Are you measuring what matters? A fresh look at Time To ... - TTFB is a metric which reports the duration between sending the request from the client to a server for a given file, and the receipt of the ...
- Time to First Byte (TTFB) | Articles - TTFB is a metric that measures the time between starting navigating to a page and when the first byte of a response begins to arrive.
Monitor AI API latency from 22 regions
Observinio runs daily probes against OpenRouter and OpenAI endpoints and emails you when latency degrades.
Set up alerts