{ "openapi": "3.0.0", "info": { "title": "Speedtest Tracker API", "version": "1.0.0" }, "paths": { "/api/v1/ookla": { "description": "Endpoints for retrieving Ookla speedtest servers and related resources." }, "/api/v1/results": { "description": "Endpoints for retrieving speedtest results.", "get": { "tags": [ "Results" ], "summary": "List all results", "operationId": "listResults", "parameters": [ { "$ref": "#/components/parameters/AcceptHeader" }, { "name": "per.page", "in": "query", "description": "Number of results per page", "required": false, "schema": { "type": "integer", "default": 25, "maximum": 500, "minimum": 1 } }, { "name": "filter[ping]", "in": "query", "description": "Filter by ping value (supports operators like >=, <=, etc.)", "required": false, "schema": { "type": "number" } }, { "name": "filter[download]", "in": "query", "description": "Filter by download speed (supports operators like >=, <=, etc.)", "required": false, "schema": { "type": "integer" } }, { "name": "filter[upload]", "in": "query", "description": "Filter by upload speed (supports operators like >=, <=, etc.)", "required": false, "schema": { "type": "integer" } }, { "name": "filter[healthy]", "in": "query", "description": "Filter by healthy status", "required": false, "schema": { "type": "boolean" } }, { "name": "filter[status]", "in": "query", "description": "Filter by status", "required": false, "schema": { "type": "string" } }, { "name": "filter[scheduled]", "in": "query", "description": "Filter by scheduled status", "required": false, "schema": { "type": "boolean" } }, { "name": "filter[start_at]", "in": "query", "description": "Filter results created on or after this date (alias for created_at>=)", "required": false, "schema": { "type": "string", "format": "date" } }, { "name": "filter[end_at]", "in": "query", "description": "Filter results created on or before this date (alias for created_at<=)", "required": false, "schema": { "type": "string", "format": "date" } }, { "name": "sort", "in": "query", "description": "Sort results by field (prefix with - for descending)", "required": false, "schema": { "type": "string", "enum": [ "ping", "-ping", "download", "-download", "upload", "-upload", "created_at", "-created_at", "updated_at", "-updated_at" ] } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResultsCollection" } } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForbiddenError" } } } }, "401": { "description": "Unauthenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UnauthenticatedError" } } } }, "406": { "description": "Not Acceptable - Missing or invalid Accept header", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotAcceptableError" } } } }, "422": { "description": "Validation failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" } } } } } } }, "/api/v1/stats": { "description": "Endpoints for viewing performance statistics.", "get": { "tags": [ "Stats" ], "summary": "Fetch aggregated Speedtest statistics", "operationId": "getStats", "parameters": [ { "$ref": "#/components/parameters/AcceptHeader" }, { "name": "start_at", "in": "query", "description": "Filter stats from this date/time (ISO 8601)", "required": false, "schema": { "type": "string", "format": "date-time" } }, { "name": "end_at", "in": "query", "description": "Filter stats up to this date/time (ISO 8601)", "required": false, "schema": { "type": "string", "format": "date-time" } } ], "responses": { "200": { "description": "Statistics fetched successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Stats" } } } }, "401": { "description": "Unauthenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UnauthenticatedError" } } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForbiddenError" } } } }, "406": { "description": "Not Acceptable - Missing or invalid Accept header", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotAcceptableError" } } } }, "422": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" } } } } } } }, "/api/v1/ookla/list-servers": { "get": { "tags": [ "Servers" ], "summary": "List available Ookla speedtest servers", "description": "Returns an array of available Ookla speedtest servers. Requires an API token with `ookla:list-servers` scope.", "operationId": "listOoklaServers", "parameters": [ { "$ref": "#/components/parameters/AcceptHeader" } ], "responses": { "200": { "description": "Servers retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServersCollection" } } } }, "401": { "description": "Unauthenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UnauthenticatedError" } } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForbiddenError" } } } }, "406": { "description": "Not Acceptable - Missing or invalid Accept header", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotAcceptableError" } } } } } } }, "/api/v1/results/{id}": { "get": { "tags": [ "Results" ], "summary": "Get a single result", "operationId": "getResult", "parameters": [ { "$ref": "#/components/parameters/AcceptHeader" }, { "name": "id", "in": "path", "description": "The ID of the result", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResultResponse" } } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForbiddenError" } } } }, "401": { "description": "Unauthenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UnauthenticatedError" } } } }, "406": { "description": "Not Acceptable - Missing or invalid Accept header", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotAcceptableError" } } } }, "404": { "description": "Result not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotFoundError" } } } } } } }, "/api/v1/results/latest": { "get": { "tags": [ "Results" ], "summary": "Get the most recent result", "operationId": "getLatestResult", "parameters": [ { "$ref": "#/components/parameters/AcceptHeader" } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Result" } } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForbiddenError" } } } }, "401": { "description": "Unauthenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UnauthenticatedError" } } } }, "406": { "description": "Not Acceptable - Missing or invalid Accept header", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotAcceptableError" } } } }, "404": { "description": "No result found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotFoundError" } } } } } } }, "/api/v1/speedtests/run": { "post": { "tags": [ "Speedtests" ], "summary": "Run a new Ookla speedtest", "operationId": "runSpeedtest", "parameters": [ { "$ref": "#/components/parameters/AcceptHeader" }, { "name": "server_id", "in": "query", "description": "Optional Ookla speedtest server ID", "required": false, "schema": { "type": "integer" } } ], "responses": { "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpeedtestRun" } } } }, "401": { "description": "Unauthenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UnauthenticatedError" } } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForbiddenError" } } } }, "406": { "description": "Not Acceptable - Missing or invalid Accept header", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotAcceptableError" } } } }, "422": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" } } } } } } }, "/api/v1/speedtests/list-servers": { "get": { "tags": [ "Speedtests" ], "summary": "List available Ookla speedtest servers", "operationId": "listSpeedtestServers", "parameters": [ { "$ref": "#/components/parameters/AcceptHeader" } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServersCollection" } } } }, "401": { "description": "Unauthenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UnauthenticatedError" } } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForbiddenError" }, "example": { "message": "You do not have permission to view speedtest servers." } } } }, "406": { "description": "Not Acceptable - Missing or invalid Accept header", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotAcceptableError" } } } } } } } }, "components": { "schemas": { "ForbiddenError": { "description": "Forbidden error response when user lacks permission", "properties": { "message": { "description": "Error message indicating lack of permission", "type": "string" } }, "type": "object" }, "NotAcceptableError": { "description": "Error response when the Accept header is missing or invalid", "properties": { "message": { "type": "string", "example": "This endpoint only accepts JSON. Please include \"Accept: application/json\" in your request headers." }, "error": { "type": "string", "example": "Unsupported Media Type" } }, "type": "object" }, "NotFoundError": { "description": "Error when a requested result is not found", "properties": { "message": { "description": "Result not found error message", "type": "string" } }, "type": "object" }, "ResultResponse": { "description": "Response for an Single Speedtest result entry", "properties": { "data": { "$ref": "#/components/schemas/Result" }, "message": { "description": "Response status message", "type": "string" } }, "type": "object" }, "Result": { "description": "Speedtest result entry", "properties": { "id": { "type": "integer" }, "service": { "type": "string" }, "ping": { "type": "number" }, "download": { "type": "integer" }, "upload": { "type": "integer" }, "download_bits": { "type": "integer" }, "upload_bits": { "type": "integer" }, "download_bits_human": { "type": "string" }, "upload_bits_human": { "type": "string" }, "benchmarks": { "type": "array", "items": { "type": "object" }, "nullable": true }, "healthy": { "type": "boolean", "nullable": true }, "status": { "type": "string" }, "scheduled": { "type": "boolean" }, "comments": { "type": "string", "nullable": true }, "data": { "description": "Nested speedtest data payload", "properties": { "isp": { "type": "string" }, "ping": { "properties": { "low": { "type": "number", "format": "float" }, "high": { "type": "number", "format": "float" }, "jitter": { "type": "number", "format": "float" }, "latency": { "type": "number", "format": "float" } }, "type": "object" }, "type": { "type": "string" }, "result": { "properties": { "id": { "type": "string" }, "url": { "type": "string", "format": "uri" }, "persisted": { "type": "boolean" } }, "type": "object" }, "server": { "properties": { "id": { "type": "integer" }, "ip": { "type": "string", "format": "ipv4" }, "host": { "type": "string" }, "name": { "type": "string" }, "port": { "type": "integer" }, "country": { "type": "string" }, "location": { "type": "string" } }, "type": "object" }, "upload": { "properties": { "bytes": { "type": "integer" }, "elapsed": { "type": "integer" }, "latency": { "properties": { "iqm": { "type": "number", "format": "float" }, "low": { "type": "number", "format": "float" }, "high": { "type": "number", "format": "float" }, "jitter": { "type": "number", "format": "float" } }, "type": "object" }, "bandwidth": { "type": "integer" } }, "type": "object" }, "download": { "properties": { "bytes": { "type": "integer" }, "elapsed": { "type": "integer" }, "latency": { "properties": { "iqm": { "type": "number", "format": "float" }, "low": { "type": "number", "format": "float" }, "high": { "type": "number", "format": "float" }, "jitter": { "type": "number", "format": "float" } }, "type": "object" }, "bandwidth": { "type": "integer" } }, "type": "object" }, "interface": { "properties": { "name": { "type": "string" }, "isVpn": { "type": "boolean" }, "macAddr": { "type": "string", "pattern": "^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$" }, "externalIp": { "type": "string", "format": "ipv4" }, "internalIp": { "type": "string", "format": "ipv4" } }, "type": "object" }, "timestamp": { "type": "string", "format": "date-time" }, "packetLoss": { "type": "number" } }, "type": "object" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "type": "object", "additionalProperties": false }, "ResultsCollection": { "description": "Paginated list of Speedtest results", "properties": { "data": { "description": "Array of result objects", "type": "array", "items": { "$ref": "#/components/schemas/Result" } }, "links": { "properties": { "first": { "type": "string" }, "last": { "type": "string" }, "prev": { "type": "string", "nullable": true }, "next": { "type": "string", "nullable": true } }, "type": "object", "additionalProperties": false }, "meta": { "properties": { "current_page": { "type": "integer" }, "from": { "type": "integer" }, "last_page": { "type": "integer" }, "links": { "type": "array", "items": { "properties": { "url": { "type": "string", "nullable": true }, "label": { "type": "string" }, "active": { "type": "boolean" } }, "type": "object", "additionalProperties": false } }, "path": { "type": "string" }, "per.page": { "type": "integer" }, "to": { "type": "integer" }, "total": { "type": "integer" } }, "type": "object", "additionalProperties": false } }, "type": "object", "additionalProperties": false }, "ServersCollection": { "description": "Collection of Ookla speedtest servers", "properties": { "data": { "description": "List of server objects", "type": "array", "items": { "properties": { "id": { "type": "string" }, "host": { "type": "string" }, "name": { "type": "string" }, "location": { "type": "string" }, "country": { "type": "string" } }, "type": "object" } }, "message": { "description": "Response status message", "type": "string" } }, "type": "object", "additionalProperties": false }, "SpeedtestRun": { "description": "A queued speedtest result", "properties": { "data": { "description": "Queued speedtest result payload", "properties": { "id": { "type": "integer" }, "service": { "type": "string" }, "ping": { "type": "number", "format": "float", "nullable": true }, "download": { "type": "integer", "nullable": true }, "upload": { "type": "integer", "nullable": true }, "benchmarks": { "type": "object", "nullable": true }, "healthy": { "type": "boolean", "nullable": true }, "status": { "type": "string" }, "scheduled": { "type": "boolean" }, "comments": { "type": "string", "nullable": true }, "data": { "description": "Additional data for queued result", "properties": { "server": { "properties": { "id": { "type": "integer", "nullable": true } }, "type": "object" } }, "type": "object" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "type": "object", "additionalProperties": false }, "message": { "description": "Response status message", "type": "string" } }, "type": "object", "additionalProperties": false }, "Stats": { "description": "Aggregated speedtest statistics", "properties": { "total_results": { "type": "integer" }, "avg_ping": { "type": "number", "format": "float" }, "avg_download": { "type": "number", "format": "float" }, "avg_upload": { "type": "number", "format": "float" }, "min_ping": { "type": "number", "format": "float" }, "min_download": { "type": "number", "format": "float" }, "min_upload": { "type": "number", "format": "float" }, "max_ping": { "type": "number", "format": "float" }, "max_download": { "type": "number", "format": "float" }, "max_upload": { "type": "number", "format": "float" } }, "type": "object" }, "UnauthenticatedError": { "description": "Error when user is not authenticated", "properties": { "message": { "description": "Unauthenticated error message", "type": "string" } }, "type": "object" }, "ValidationError": { "description": "Validation failed due to invalid server_id input", "properties": { "message": { "description": "Validation failed due to invalid server_id input", "type": "string" } }, "type": "object" } }, "parameters": { "AcceptHeader": { "name": "Accept", "in": "header", "description": "Must be \"application/json\" - this API only accepts and returns JSON", "required": true, "schema": { "type": "string", "default": "application/json" } } }, "securitySchemes": { "bearerAuth": { "type": "http", "bearerFormat": "JWT", "scheme": "bearer" } } }, "tags": [ { "name": "Results", "description": "Endpoints for accessing and filtering speedtest results. Requires API token with `results:read` scope." }, { "name": "Speedtests", "description": "Endpoints for running speedtests and listing servers." }, { "name": "Stats", "description": "Endpoints for retrieving aggregated statistics and performance metrics. Requires `speedtests:read` token scope." }, { "name": "Servers", "description": "Servers" } ] }