turbometrics offers a REST API for programmatic access to scans, domains and account data. Authentication works via bearer tokens, which you manage under Profile → API & apps.
Authentication
Add your API token to every request as an Authorization header:
Authorization: Bearer {your-token}
The API always responds with JSON. Without a valid token you get 401 Unauthenticated.
Base URL
https://turbometrics.io/api/v1
Rate limiting
The daily request limit depends on your plan:
| Plan | Daily limit |
|---|---|
| Starter | 500 |
| Pro | 5,000 |
| Agency | Unlimited |
If you exceed it, you get 429 Too Many Requests:
{
"error": "Daily API limit reached",
"limit": 500,
"reset_at": "2026-04-01T23:59:59+02:00"
}
The counter is reset daily at midnight.
Endpoints
GET /me
Returns account information and the current API usage.
Example:
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/me
Response:
{
"data": {
"id": 42,
"name": "John Doe",
"email": "[email protected]",
"plan": {
"key": "starter",
"label": "Starter",
"api_enabled": true,
"api_daily_limit": 500
},
"api_usage": {
"used_today": 12,
"limit_today": 500,
"reset_at": "2026-04-01T23:59:59+02:00"
}
}
}
GET /scans
Returns a paginated list of your scans.
Parameters:
| Parameter | Type | Description |
|---|---|---|
limit |
int | Results per page, max. 50 (default: 20) |
domain |
string | Filters by URL content |
status |
string | queued, running, finished, failed |
page |
int | Page number |
Example:
curl -H "Authorization: Bearer {token}" \
"https://turbometrics.io/api/v1/scans?limit=10&status=finished"
Response:
{
"data": [
{
"public_id": "01KN3X...",
"report_url": "https://turbometrics.io/scan/01KN3X...",
"status": "finished",
"submitted_url": "https://example.com/",
"region": "de-fsn1",
"auth_type": null,
"requested_at": "2026-03-31T00:29:00+02:00",
"finished_at": "2026-03-31T00:29:45+02:00",
"result": {
"scores": {
"overall": 87,
"speed": 100,
"images": 93,
"caching": 89,
"wordpress": 100,
"technical": 70
}
}
}
],
"meta": {
"current_page": 1,
"per_page": 10,
"total": 143,
"last_page": 15
}
}
POST /scans
Starts a new scan.
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | yes | The URL to scan |
region |
string | no | Scan location (e.g. de-fsn1, de-nbg1) |
public |
bool | no | Publicly visible (default: false) |
force |
bool | no | Force a fresh scan even if a recent result exists (default: false) |
auth |
object | no | Authentication for protected pages (requires the Pro plan, see below) |
Note on caching: without force: true, an existing scan result for the same URL is returned if it is less than 24 hours old. The response then contains "cached": true and HTTP 200 instead of 202.
Example (normal scan):
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}' \
https://turbometrics.io/api/v1/scans
Example (force a fresh scan):
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "force": true}' \
https://turbometrics.io/api/v1/scans
Response — new scan (202 Accepted):
{
"data": {
"id": "01KN3X...",
"status": "queued",
"url": "https://example.com/",
"cached": false,
"auth_type": null
}
}
Response — cache hit (200 OK):
{
"data": {
"id": "01KN3X...",
"status": "finished",
"url": "https://example.com/",
"cached": true,
"auth_type": null
}
}
The scan is processed asynchronously. Call GET /scans/{id} to check the status.
Scan with authentication (Pro plan and above)
Pages protected by HTTP basic auth or by a custom HTTP header can be scanned with the optional auth object. Requires the Pro plan.
Fields in the auth object:
| Field | Type | Description |
|---|---|---|
type |
string | "basic" or "header" |
username |
string | User name (only with type: "basic") |
password |
string | Password (only with type: "basic") |
header_name |
string | Name of the HTTP header (only with type: "header") |
header_value |
string | Value of the HTTP header (only with type: "header") |
Example — HTTP basic auth:
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://staging.example.com",
"auth": {
"type": "basic",
"username": "admin",
"password": "secret"
}
}' \
https://turbometrics.io/api/v1/scans
Example — HTTP header:
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://staging.example.com",
"auth": {
"type": "header",
"header_name": "X-Preview-Key",
"header_value": "abc123"
}
}' \
https://turbometrics.io/api/v1/scans
Response with authentication:
{
"data": {
"id": "01KN3X...",
"status": "queued",
"url": "https://staging.example.com/",
"cached": false,
"auth_type": "basic"
}
}
The response contains auth_type as an indicator — credentials are never returned. Scans with authentication are automatically treated as private.
Without the scan_auth feature (Free / Starter), the auth object is rejected with 403 Forbidden.
GET /scans/{id}
Returns the details of a single scan.
{id} is the public_id (e.g. 01KN3X...).
report_url points to the report in the web interface. For private scans the link
only works for the signed-in owner — to pass a report on to someone else, use
share_url (see Share links).
Example:
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/scans/01KN3X...
Response (running scan):
{
"data": {
"public_id": "01KN3X...",
"report_url": "https://turbometrics.io/scan/01KN3X...",
"status": "running",
"submitted_url": "https://example.com/",
"region": "de-fsn1",
"auth_type": null
}
}
Response (finished scan):
{
"data": {
"public_id": "01KN3X...",
"report_url": "https://turbometrics.io/scan/01KN3X...",
"status": "finished",
"is_public": false,
"submitted_url": "https://example.com/",
"region": "de-fsn1",
"auth_type": null,
"share_enabled": false,
"share_url": null,
"requested_at": "2026-03-31T00:29:00+02:00",
"finished_at": "2026-03-31T00:29:45+02:00",
"result": {
"final_url": "https://example.com/",
"final_host": "example.com",
"http_status": 200,
"scores": {
"overall": 87,
"speed": 100,
"images": 93,
"caching": 89,
"wordpress": 100,
"technical": 70
},
"summary_short": "The page loads quickly, but has room for improvement in technical metrics.",
"summary_long": "...",
"metrics": {
"ttfb_ms": 182,
"desktop": {
"fcp_ms": 412,
"lcp_ms": 830,
"cls": 0.02,
"tbt_ms": 0,
"request_count": 34,
"total_bytes": 512000
},
"mobile": {
"fcp_ms": 980,
"lcp_ms": 2100,
"cls": 0.04,
"tbt_ms": 120,
"request_count": 34,
"total_bytes": 512000
}
},
"screenshots": {
"desktop_url": "https://turbometrics.io/scan/01KN3X.../screenshot?profile=desktop",
"mobile_url": "https://turbometrics.io/scan/01KN3X.../screenshot?profile=mobile",
"filmstrip": {
"desktop": [
"https://turbometrics.io/scan/01KN3X.../strip/desktop/0?...",
"https://turbometrics.io/scan/01KN3X.../strip/desktop/1?..."
],
"mobile": [
"https://turbometrics.io/scan/01KN3X.../strip/mobile/0?..."
]
},
"filmstrip_frames": {
"desktop": [
{ "url": "https://turbometrics.io/scan/01KN3X.../strip/desktop/0?...", "ms": 0, "event": null },
{ "url": "https://turbometrics.io/scan/01KN3X.../strip/desktop/1?...", "ms": 850, "event": "lcp" }
],
"mobile": [
{ "url": "https://turbometrics.io/scan/01KN3X.../strip/mobile/0?...", "ms": 0, "event": "fcp" }
]
}
},
"findings": [
{
"category": "images",
"code": "unoptimized_images",
"severity": "warning",
"title": "Images not optimised",
"message": "3 images could be smaller.",
"recommendation": "Use WebP and compress images before upload."
}
]
}
}
}
POST /scans/{id}/share/enable
Enables the share link for a scan and returns the public share URL. Requires at least the Starter plan.
{id} is the public_id of the scan (e.g. 01KN3X...).
Example:
curl -X POST \
-H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/scans/01KN3X.../share/enable
Response:
{
"data": {
"share_enabled": true,
"share_url": "https://turbometrics.io/s/abc123xyz012"
}
}
The token stays stable — enabling it again after disabling it uses the same URL.
POST /scans/{id}/share/disable
Disables the share link. The share token is kept internally, so that reactivating it later delivers the same URL.
Example:
curl -X POST \
-H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/scans/01KN3X.../share/disable
Response:
{
"data": {
"share_enabled": false
}
}
GET /domains
Returns a paginated list of your monitored scan targets.
Example:
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/domains
Response:
{
"data": [
{
"id": 7,
"host": "example.com",
"url": "https://example.com/",
"schedule": "daily",
"is_active": true,
"last_dispatched_at": "2026-03-31T08:00:00+02:00"
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 5,
"last_page": 1
}
}
GET /domains/{id}/history
Returns the last 30 finished scans for a scan target.
{id} is the numeric ID from GET /domains.
Example:
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/domains/7/history
Response:
{
"data": [
{
"score": 87,
"created_at": "2026-03-31T08:01:23+02:00",
"region": "de-fsn1"
},
{
"score": 84,
"created_at": "2026-03-30T08:00:55+02:00",
"region": "de-fsn1"
}
]
}
GET /alerts
Returns a paginated list of your alerts.
Parameters:
| Parameter | Type | Description |
|---|---|---|
status |
string | open (open, not dismissed), resolved, unread |
severity |
string | critical, warning |
limit |
int | Results per page, max. 50 (default: 20) |
page |
int | Page number |
Example:
curl -H "Authorization: Bearer {token}" \
"https://turbometrics.io/api/v1/alerts?status=open&severity=critical"
Response:
{
"data": [
{
"id": 123,
"type": "score_below_threshold",
"severity": "critical",
"title": "Score below threshold",
"message": "Score 34 below the critical threshold of 60.",
"is_read": false,
"is_dismissed": false,
"url": "https://example.com/",
"host": "example.com",
"scan_id": "01KN3X...",
"created_at": "2026-03-31T08:01:23+02:00",
"resolved_at": null
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 3,
"last_page": 1
}
}
GET /alerts/{id}
Returns a single alert.
Example:
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/alerts/123
Response: the same structure as a single element from GET /alerts.
POST /alerts/mark-read
Marks alerts as read. Without ids, all unread alerts of the user are marked.
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
ids |
array | no | Array of alert IDs; missing → all |
Example (specific alerts):
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"ids": [123, 124]}' \
https://turbometrics.io/api/v1/alerts/mark-read
Example (mark all as read):
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{}' \
https://turbometrics.io/api/v1/alerts/mark-read
Response:
{
"data": {
"marked_read": 5
}
}
GET /rum/sites
Returns a paginated list of your live-data websites. Starter plan and above only.
Parameters:
| Parameter | Type | Description |
|---|---|---|
limit |
int | Results per page, max. 50 (default: 20) |
page |
int | Page number |
Example:
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/rum/sites
Response:
{
"data": [
{
"id": 1,
"domain": "turbometrics.io",
"active": true,
"embed_snippet": "<script src=\"https://turbometrics.io/tm.min.js\" data-site-id=\"...\" async></script>",
"pageviews_this_month": 988,
"monthly_limit": 1000000,
"created_at": "2026-04-01T00:00:00+02:00"
}
],
"meta": { "current_page": 1, "per_page": 20, "total": 2, "last_page": 1 }
}
GET /rum/sites/{id}
Returns the details of a single live-data website.
Example:
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/rum/sites/1
GET /rum/sites/{id}/summary
Returns p75 values and the Core Web Vitals status for a website.
Parameters:
| Parameter | Type | Description |
|---|---|---|
period |
string | 24h, 7d, 30d (default: 24h) |
device |
string | all, desktop, mobile, tablet (default: all) |
Example:
curl -H "Authorization: Bearer {token}" \
"https://turbometrics.io/api/v1/rum/sites/1/summary?period=24h&device=all"
Response:
{
"data": {
"domain": "turbometrics.io",
"period": "24h",
"device": "all",
"cwv_pass": true,
"cwv_insufficient_data": false,
"metrics": {
"LCP": { "p75": 226, "rating": "good", "samples": 127 },
"CLS": { "p75": 0.0, "rating": "good", "samples": 12 },
"INP": { "p75": 112, "rating": "good", "samples": 16 },
"FCP": { "p75": 201, "rating": "good", "samples": 26 },
"TTFB": { "p75": 143, "rating": "good", "samples": 27 }
}
}
}
cwv_pass is true when all three Core Web Vitals (LCP, CLS, INP) are within the target range and each has at least 20 measurements. With too little data, cwv_pass is null and cwv_insufficient_data is true.
GET /rum/sites/{id}/history
Returns daily p75/p50 values for one metric as a time series.
Parameters:
| Parameter | Type | Description |
|---|---|---|
metric |
string | Required: LCP, CLS, INP, FCP, TTFB |
days |
int | 7, 30, 90 (default: 30) |
device |
string | all, desktop, mobile, tablet (default: all) |
Example:
curl -H "Authorization: Bearer {token}" \
"https://turbometrics.io/api/v1/rum/sites/1/history?metric=LCP&days=30"
Response:
{
"data": [
{ "date": "2026-04-04", "p75": 226, "p50": 180, "samples": 127 },
{ "date": "2026-04-03", "p75": 240, "p50": 195, "samples": 98 }
],
"meta": { "metric": "LCP", "days": 30, "device": "all" }
}
GET /rum/sites/{id}/pages
Returns the slowest pages by p75 for one metric.
Parameters:
| Parameter | Type | Description |
|---|---|---|
metric |
string | LCP, FCP, TTFB (default: LCP) |
period |
string | 24h, 7d (default: 24h) |
limit |
int | 1–100 (default: 25) |
Example:
curl -H "Authorization: Bearer {token}" \
"https://turbometrics.io/api/v1/rum/sites/1/pages?metric=LCP&limit=10"
Response:
{
"data": [
{ "path": "/", "p75": 1437, "samples": 3 },
{ "path": "/preise", "p75": 98, "samples": 12 }
],
"meta": { "metric": "LCP", "period": "24h" }
}
GET /rum/sites/{id}/alerts
Returns the configured alerts for a live-data website.
Example:
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/rum/sites/1/alerts
Response:
{
"data": [
{
"id": 209,
"title": "turbometrics.io — Auto-Alert",
"mode": "auto",
"metric": null,
"threshold": null,
"window_minutes": 60,
"min_samples": 20,
"consecutive_threshold": 2,
"status": "active",
"emailed_at": null,
"resolved_at": null,
"created_at": "2026-04-05T10:00:00+02:00"
}
]
}
Possible values for status: active (configured, never triggered), triggered (currently triggered), resolved (resolved).
Error codes
| Code | Meaning |
|---|---|
| 401 | No token or an invalid one |
| 403 | The plan does not support the API (Starter or higher needed) — or an auth object was passed without a Pro plan |
| 404 | Resource not found |
| 422 | Validation error (e.g. an invalid URL) |
| 429 | Hourly scan limit or daily API limit exceeded |
| 500 | Internal error |
Example 422:
{
"message": "The url field is required.",
"errors": {
"url": ["The url field is required."]
}
}
Code examples
curl
# Start a scan
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "region": "de-fsn1"}' \
https://turbometrics.io/api/v1/scans
# Scan with HTTP basic auth (Pro plan and above)
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"url": "https://staging.example.com", "auth": {"type": "basic", "username": "admin", "password": "secret"}}' \
https://turbometrics.io/api/v1/scans
# Scan with an HTTP header (Pro plan and above)
curl -X POST \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"url": "https://staging.example.com", "auth": {"type": "header", "header_name": "X-Preview-Key", "header_value": "abc123"}}' \
https://turbometrics.io/api/v1/scans
# Check the status
curl -H "Authorization: Bearer {token}" \
https://turbometrics.io/api/v1/scans/01KN3X...
# The last 5 scans
curl -H "Authorization: Bearer {token}" \
"https://turbometrics.io/api/v1/scans?limit=5&status=finished"
PHP
$token = 'your-api-token';
$base = 'https://turbometrics.io/api/v1';
// Start a scan
$ch = curl_init("$base/scans");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['url' => 'https://example.com']),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
$scanId = $response['data']['id']; // e.g. "01KN3X..."
// Start a scan with HTTP basic auth (Pro plan and above)
$ch = curl_init("$base/scans");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://staging.example.com',
'auth' => [
'type' => 'basic',
'username' => 'admin',
'password' => 'secret',
],
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
// auth_type in the response shows "basic"; credentials are never returned
echo $response['data']['auth_type']; // "basic"
// Check the status
$ch = curl_init("$base/scans/$scanId");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Accept: application/json',
],
]);
$scan = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $scan['data']['status']; // queued | running | finished | failed
Rather not write code? Use the MCP server
If you prefer to query scans, alerts and live data in natural language, you can connect turbometrics directly to Claude Desktop and other AI tools — without writing your own API calls.
Connecting apps (OAuth 2.1)
The sections above describe access to your own account using a token you created yourself. If you are building an application that acts on behalf of other turbometrics users, there has been a second route since August 2026: OAuth 2.1. The user clicks "Allow" once and never has to copy a token.
The access token issued is an ordinary bearer token and works against all endpoints described above — not just the MCP server.
| Document | Address |
|---|---|
| Authorization server | https://turbometrics.io/.well-known/oauth-authorization-server |
| Registration | POST https://turbometrics.io/oauth/register |
| Authorisation | https://turbometrics.io/oauth/authorize |
| Token | POST https://turbometrics.io/oauth/token |
What you need to know:
- PKCE with
S256is mandatory. Requests without acode_challengeare refused. - Public clients, no secret. Your app registers itself via the registration endpoint (RFC 7591) or identifies itself through a Client ID Metadata Document.
- Redirect URIs must use
https— orhttpon a loopback address, for applications that open a local port. - Access tokens are valid for one hour, refresh tokens for 30 days. Refresh tokens rotate: each renewal gives you a new one and retires the old one.
- The user needs at least the Starter plan and will see your app under Profile → API & apps, where access can be revoked at any time.
No OpenID Connect: there is no id_token and no userinfo endpoint. For
"who is this?", call GET /api/v1/me after signing in.
Token management
You create and manage API tokens under Profile → API & apps:
- Give every token a descriptive name (e.g.
Monitoring-Script,CI/CD) - Set an expiry date for security-critical environments
- The token value is shown only once after creation
- Delete compromised tokens immediately and create new ones