diff --git a/README.md b/README.md index 244b5757..23447219 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ POST https://api.globalping.io/v1/measurements } ``` -[Read the full API documentation](docs) and [explore our dev demo](https://api.globalping.io/demo/) +[Read the full API documentation](https://www.jsdelivr.com/docs/api.globalping.io) and [explore our dev demo](https://api.globalping.io/demo/) ### Slack App diff --git a/config/default.cjs b/config/default.cjs index 1430f2f1..9b0749e8 100644 --- a/config/default.cjs +++ b/config/default.cjs @@ -1,6 +1,7 @@ module.exports = { server: { host: 'https://api.globalping.io', + docsHost: 'https://www.jsdelivr.com', port: 3000, processes: 2, }, diff --git a/docs/CLIENT_GUIDELINES.md b/docs/CLIENT_GUIDELINES.md deleted file mode 100644 index 59f06020..00000000 --- a/docs/CLIENT_GUIDELINES.md +++ /dev/null @@ -1,13 +0,0 @@ -# Globalping API Client Guidelines - - - Set the `inProgressUpdates` option to `true` if the application is running in interactive mode so that the user sees the results right away. - - If the application is interactive by default but also implements a "CI" mode to be used in scripts, do not set the flag in the CI mode. - - Use the following algorithm for measurement result pooling: - 1. Request the measurement status. - 2. If the status is `in-progress`, wait 500 ms and repeat from step 1. Note that it is important to wait 500 ms *after* receiving the response, instead of simply using an "every 500 ms" interval. For large measurements, the request itself may take a few hundred milliseconds to complete. - 3. If the status is anything else, stop. The measurement is no longer running. Any value other than `in-progress` is final. - -Additional guidelines for non-browser based apps: - - Set a `User-Agent` header. The recommended format and approach is [as here](https://github.com/jsdelivr/data.jsdelivr.com/blob/60c5154d26c403ba9dd403a8ddc5e42a31931f0d/config/default.js#L9). - - Set an `Accept-Encoding` header with a value of either `br` (preferred) or `gzip`, depending on what your client can support. The compression has a significant impact on the response size. - - When requesting the measurement status, implement ETag-based client-side caching using the `ETag`/`If-None-Match` headers. diff --git a/docs/README.md b/docs/README.md index 89b9dc75..2a41073b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,27 +1,4 @@ # Globalping API -**API domain**: `api.globalping.io` - -**API prefix**: `/v1` - -If you are implementing an application interacting with the API, please also see the -[client guidelines](CLIENT_GUIDELINES.md). - -## Open Endpoints - -Open endpoints require no Authentication. - -### probe - -- [show probe list](probes/get.md): `GET /v1/probes` - -### measurement - -- [post measurement](measurement/post-create.md): `POST /v1/measurements` -- [get measurement](measurement/get.md): `GET /v1/measurements/:id` - -### schemas - -- [Location filters](measurement/schema/location.md) -- [Measurement Request](measurement/schema/request.md) -- [Measurement Response](measurement/schema/response.md) +The documentation has moved to https://www.jsdelivr.com/docs/api.globalping.io. +To edit it, see the [OpenAPI document](../public/v1/spec.yaml). diff --git a/docs/measurement/get.md b/docs/measurement/get.md deleted file mode 100644 index 00e40d0a..00000000 --- a/docs/measurement/get.md +++ /dev/null @@ -1,124 +0,0 @@ -# get measurement - -Get the current state of the measurement. - -## request - -**method**: `GET` - -**url**: `/v1/measurements/:id` - -### parameters - -**params**: -- `:id` - Id of the measurement request. Returned by [POST /v1/measurements](./post-create.md) - -## success response - -**status code**: `200 OK` - -**content**: response will contain results from all requested probes, alongside some basic metadata of the request. For more detailed schema description, please follow the guide on [MEASUREMENT RESPONSE SCHEMA](./schema/response.md) - -### schema - -``` -[ - { - id: string - type: string - status: string - createdAt: number - updatedAt: number - target: string - limit?: number - probesCount: number - locations?: Location[] - measurementOptions?: Object - results: Result[] - } -] -``` - -### example - -```json -GET https://api.globalping.io/v1/measurements/tEaUg3vYnOu2exVC - -{ - "id": "tEaUg3vYnOu2exVC", - "type": "ping", - "status": "finished", - "createdAt": "2022-07-17T16:19:52.909Z", - "updatedAt": "2022-07-17T16:19:52.909Z", - "target": "google.com", - "limit": 1, - "probesCount": 1, - "measurementOptions": { - "packets": 3 - }, - "results": [ - { - "probe": { - "continent": "AF", - "region": "southern africa", - "country": "ZA", - "state": null, - "city": "cape town", - "asn": 16509, - "longitude": 18.4232, - "latitude": -33.9258, - "network": "amazon.com inc.", - "tags": ["af-south-1"] - }, - "result": { - "status": "finished", - "resolvedAddress": "172.217.170.14", - "times": [ - { - "ttl": 108, - "time": 16.5 - }, - { - "ttl": 108, - "time": 16.5 - }, - { - "ttl": 108, - "time": 16.5 - } - ], - "min": 16.474, - "avg": 16.504, - "max": 16.543, - "loss": 0, - "rawOutput": "PING google.com (172.217.170.14) 56(84) bytes of data.\n64 bytes from 172.217.170.14: icmp_seq=1 ttl=108 time=16.5 ms\n64 bytes from 172.217.170.14: icmp_seq=2 ttl=108 time=16.5 ms\n64 bytes from 172.217.170.14: icmp_seq=3 ttl=108 time=16.5 ms\n\n--- google.com ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 402ms\nrtt min/avg/max/mdev = 16.474/16.504/16.543/0.028 ms" - } - } - ] -} -``` - -## error response (404) - -**condition**: requested measurement was not found in the database. - -**status code**: `404 Not Found` - -``` -(no body) -``` - -## error response (500) - -**condition**: unknown or internal error occurred. - -**status code**: `500 Internal Server Error` - -```json -{ - "error": { - "message": "Internal Server Error", - "type": "api_error" - } -} -``` diff --git a/docs/measurement/post-create.md b/docs/measurement/post-create.md deleted file mode 100644 index 89571527..00000000 --- a/docs/measurement/post-create.md +++ /dev/null @@ -1,162 +0,0 @@ -# post measurement - -Creates an on-demand measurement to run immediately. - -## request - -**method**: `POST` - -**url**: `/v1/measurements` - -### parameters - -**headers**: -- `content-type: application/json` (required) - -**body**: - -schema: - -below is presented a schema containing all possible input values; some are general, while others are utilised by a specific query type. Please, consult the [SCHEMA GUIDE](./schema/request.md) for a more detailed description. -``` -{ - limit: number - locations: Locations[] - type: string - target: string - inProgressUpdates?: boolean - measurementOptions: { - query?: { - type?: string - } - request?: { - headers?: Object - path?: string - host?: string - query?: string - method?: string - } - port?: number - resolver?: string - trace?: boolean - protocol?: string - packets?: number - } -} -``` -example: -```json -POST https://api.globalping.io/v1/measurements -{ - "target": "jsdelivr.com", - "type": "ping", - "measurementOptions": { - "packets": 10, - }, - "limit": 10, - "locations": [ - { "country": "gb" } - ] -} -``` -for `Locations` schema, please see [LOCATION SCHEMA](./schema/location.md). - -## success response - -**status code**: `202 Accepted` - -**content**: response will contain an Id number of your measurement, and total number of probes assigned to your query. The count of assigned probes might vary from what you requested. A URL pointing to the measurement status is sent in the `Location` header. - -### schema - -``` -{ - id: string, - probesCount: number, -} -``` - -### example - -```json -POST https://api.globalping.io/v1/measurements -{ - "id": "PY5fMsREMmIq45VR", - "probesCount": 1, -} -``` - -headers: - -``` - Location: https://api.globalping.io/v1/measurements/PY5fMsREMmIq45VR -``` - -## error response (no probes found) - -**condition**: if provided location doesn't have probes - e.g. `{ locations: [ 'magic': 'not a real place' ] }` - -**status code**: `422 Unprocessable Entity` - -```json -{ - "error": { - "message": "No suitable probes found", - "type": "no_probes_found" - } -} -``` - -## error response (validation failed) - -**condition**: if provided data doesn't match the schema - e.g. mismatching `target` format. - -**status code**: `400 Bad Request` - -### schema - -``` -{ - error: { - message: string - type: string - params: { - [key: string]: string - } - } -} -``` - -### example - -```json -{ - "error": { - "message": "Validation Failed", - "type": "validation_error", - "params": { - "measurement": "\"measurement\" does not match any of the allowed types" - } - } -} -``` - -## error response (500) - -**condition**: unknown or internal error occurred - -**status code**: `500 Internal Server Error` - -```json -{ - "error": { - "message": "Internal Server Error", - "type": "api_error" - } -} -``` - -## notes -- The measurement Id number is non retrievable. -- The measurement Id number can be used to fetch the query result. -- Measurements are kept for up-to full 7 days. diff --git a/docs/measurement/schema/location.md b/docs/measurement/schema/location.md deleted file mode 100644 index 87bdbd58..00000000 --- a/docs/measurement/schema/location.md +++ /dev/null @@ -1,248 +0,0 @@ -# Location Schema - -supported `type` values: -- [`continent`](#continent-query) -- [`region`](#region-query) -- [`country`](#country-query) -- [`state`](#state-query) -- [`city`](#city-query) -- [`network`](#network-query) -- [`asn`](#asn-query) -- [`tags`](#-tags-query) -- [`magic`](#magic-query) - -Location filters can be joined, for more accurate queries. Consider the following example: - -```json - { "country": "gb", "city": "london", "magic": "virgin" } -``` - -it will match a probe located in `London, England`, running on `Virgin Media Limited` network. - -

Continent

- -### rules - -- typeof `string` -- case insensitive -- must match one of the pre-defined values - -### available values - -`continent` type accepts any value matching continent name in `ISO-2` format. - -### example - -```json -{ "continent": "eu" } -``` - -

Region

- -### rules - -- typeof string -- case insensitive -- must match one of the pre-defined values - -### available values - -- `northern europe` -- `southern europe` -- `western europe` -- `eastern europe` -- `southern asia` -- `south-eastern asia` -- `western asia` -- `eastern asia` -- `central asia` -- `western africa` -- `eastern africa` -- `southern africa` -- `northern africa` -- `middle africa` -- `central america` -- `northern america` -- `south america` -- `australia and new zealand` -- `caribbean` -- `polynesia` -- `melanesia` -- `micronesia` - -### example - -```json -{ "region": "eastern africa" } -``` - -

Country

- -### rules - -- typeof `string` -- case insensitive -- must match one of the pre-defined values - -### available values - -`country` type accepts any value matching country name in `ISO-2` format. - -### example - -```json -{ "country": "fr" } -``` - -

State

- -### rules - -- typeof `string` -- case insensitive -- must match one of the pre-defined values -- only applicable to USA states - -### available values - -`state` type accepts any value matching state name in `ISO-2` format. - -### example - -```json -{ "state": "tx" } -``` - -

City

- -### rules - -- typeof `string` -- case insensitive -- at least 1 character long -- max 128 characters long - -### example - -```json -{ "city": "Austin" } -``` - -

Network

- -### rules - -- typeof `string` -- case insensitive -- at least 1 character long -- max 128 characters long - -### example - -```json -{ "network": "virgin media limited" } -``` - -

Tags

- -### rules - -- typeof: array of `string`s -- case insensitive -- at least 1 character long -- max 128 characters long - -### example - -```json -{ "tags": ["us-east-1"] } -``` - -

ASN

- -### rules - -- typeof `number` - -### example - -```json -{ "asn": 1337 } -``` - -

Magic

- -unlike other location queries, `magic` query doesn't attempt to match a specific value, but rather a pool of available matches, contained within a pre-defined array. It works by finding a partial string match of any of the above described variables. It also supports country matching based on [`Iso2`/`Iso3`](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes), common `aliases` and their full, official names format. Aliases also apply to `networks`, as well as combined matches. - -A full list of aliases can be found here: -- [`countries`](https://github.com/jsdelivr/globalping/blob/master/src/lib/location/countries.ts) -- [`networks`](https://github.com/jsdelivr/globalping/blob/master/src/lib/location/networks.ts) - -### rules - -- typeof `string` -- inputs are always converted to lowercase -- `ASN` starts with `as` prefix (`as123`) -- `latitude`/`longitude` fields are excluded -- combined matches have to be joined with `+` sign - -### examples - -Both of the following queries will match `DE` - -```json -{ "magic": "ger" }, -{ "magic": "deu" } -``` - -both of the following queries would match `amazon technologies inc.` network - -```json -{ "magic": "aws" }, -{ "magic": "amazon" } -``` - -magic queries can be combined. The following query will match server in `Belgium` hosted at `Google Cloud` DC. - -```json -{ "magic": "google+belgium" } -``` - -you can also query tags in magic field - -```json -{ "magic": "gcp-europe-west1+belgium" } -``` - -## Multiple locations - -Note that you can pass multiple `location` objects inside `locations` array to achieve `OR` behaviour. - -### examples -This query will match probes either from `gb` or `de`. - -```json -{ - "target": "jsdelivr.com", - "type": "ping", - "locations": [{ - "country": "gb" - }, { - "country": "de" - }] -} -``` - -This query will match probes either tagged with `aws-eu-west-2` or `gcp-europe-west2` - -```json -{ - "target": "jsdelivr.com", - "type": "ping", - "locations": [{ - "tags": ["aws-eu-west-2"] - }, { - "tags": ["gcp-europe-west2"] - }] -} -``` diff --git a/docs/measurement/schema/request.md b/docs/measurement/schema/request.md deleted file mode 100644 index 69093241..00000000 --- a/docs/measurement/schema/request.md +++ /dev/null @@ -1,602 +0,0 @@ -# Measurement Request Schema - -supported `type` values: -- [`ping`](#ping) -- [`traceroute`](#traceroute) -- [`dns`](#dns) -- [`mtr`](#mtr) -- [`http`](#http) - -## shared values - -### target - -A public endpoint on which tests should be executed. In most cases, it would be a hostname or IPv4 address. Its validation rules might differ depending on the query type. - -**key**: `target` - -**required**: `true` - -**rules**: -- typeof `string` -- `FQDN` or `IPv4/noCIDR Address` -- public address - -```json - "target": "globalping.io" -``` - -### limit - -Specifies the global limit of probes. - -Global limit controls the maximum number of tests the server will perform and doesn't guarantee availability; if there aren't enough probes available in specified locations, the result count might be lower. - -**key**: `limit` - -**required**: `false` - -**rules**: -- typeof `number` -- min `1` -- max `500` - -```json - "limit": 5 -``` - -### inProgressUpdates - -Specifies if the results of the measurement should be updated while being in progress. - -By default results of the tests are populated to the measurement object only after the finish of the test. For more interactive applications that want to present real-time `rawOutput` updates to the user (e.g. [globalping-cli](https://github.com/jsdelivr/globalping-cli)) `inProgressUpdates` can be enabled. Note that only the top 5 tests from the `results` array will be update in real-time. - -**key**: `inProgressUpdates` - -**default**: `false` - -**required**: `false` - -**rules**: -- typeof `boolean` - -```json - "inProgressUpdates": true -``` - -### locations - -Specifies a list of desired locations from which tests should be run. The server distributes probes based on its preconfigured geo-weight algorithm if none is provided. - -Each location filter is non-complementary and defines an individual set of probes. An optional `limit` key/value allows for more precise geo-queries. - -If no match is found - the server skips that geo. - -**key**: `locations` - -**required**: `false` - -**rules**: -- typeof `Location[]` -- each `Location` object must match one of the pre-defined types - -**Allowed values**: - -Please, see [LOCATION SCHEMA](./location.md) document for more details. - - -```json - "locations": [ - { - "continent": "eu", - "limit": 10 - }, - { - "network": "virgin media limited", - "limit": 1 - }, - { - "magic": "aws", // alias - "limit": 1 - }, - { - "magic": "pol", // (poland) partial match - "limit": 1 - } - ] -``` - -

PING

- -**type**: `ping` - -Implementation of the native `ping` command. - -The `ping` command sends an ICMP ECHO_REQUEST to obtain an ICMP ECHO_RESPONSE from a host or gateway. - -example: - -```json -{ - "type": "ping", - "target": "google.com", - "measurementOptions": { - "packets": 6 - }, - "locations": [], - "limit": 5 -} -``` - -### packets - -Specifies the desired amount of `ECHO_REQUEST` packets to be sent. - -``` -Stop after sending count ECHO_REQUEST packets. With deadline option, ping waits for count ECHO_REPLY packets, until the timeout expires. -``` - -**key**: `measurementOptions.packets` - -**default**: `3` - -**required**: `false` - -**rules**: -- typeof `number` -- min `1` -- max `16` - -```json - "packets": 5 -``` - -

TRACEROUTE

- -**type**: `traceroute` - -Implementation of the native `traceroute` command. - -traceroute tracks the route packets taken from an IP network on their way to a given host. It utilizes the IP protocol's time to live (TTL) field and attempts to elicit an ICMP TIME_EXCEEDED response from each gateway along the path to the host. - -example: -```json -{ - "type": "traceroute", - "target": "google.com", - "measurementOptions": { - "protocol": "TCP", - "port": 80 - }, - "locations": [], - "limit": 1 -} -``` - -### protocol - -Specifies the protocol used for tracerouting. - -**key**: `measurementOptions.protocol` - -**default**: `ICMP` - -**required**: `false` - -**available values**: -- `ICMP` (default) -- `TCP` -- `UDP` - -**rules**: -- typeof `string` -- must match one of the pre-defined values - -### port - -Specifies the value of the `-p` flag. Only applicable for `TCP` protocol. - -``` -For TCP and others specifies just the (constant) destination port to connect. -``` - -**key**: `measurementOptions.port` - -**default**: `80` - -**required**: `false` - -**rules**: -- typeof `number` - -```json - "port": 5 -``` - -

DNS

- -**type**: `dns` - -Implementation of the native `dig` command. - -Performs DNS lookups and displays the answers that are returned from the name server(s) that were queried. - -**warning**: -DNS specific values have to be contained within `measurementOptions.query` object. - -example: -```json -{ - "type": "dns", - "target": "google.com", - "measurementOptions": { - "protocol": "UDP", - "port": 53, - "resolver": "1.1.1.1", - "query": { - "type": "A" - } - }, - "locations": [], - "limit": 1 -} -``` - -### target - -The final destination of the request. - -**key**: `target` - -**required**: `true` - -**rules**: -- typeof `string` -- `FQDN` - -```json - "target": "globalping.io" -``` - - -### type - -Specifies the DNS type for which to look for. - -**key**: `measurementOptions.query.type` - -**default**: `A` - -**required**: `false` - -**available values**: -- `A` -- `AAAA` -- `ANY` -- `CNAME` -- `DNSKEY` -- `DS` -- `MX` -- `NS` -- `NSEC` -- `PTR` -- `RRSIG` -- `SOA` -- `TXT` -- `SRV` - -**rules**: -- typeof `string` -- must match one of the pre-defined values - -### protocol - -Specifies the protocol used for DNS lookup. - -**key**: `measurementOptions.protocol` - -**default**: `UDP` - -**required**: `false` - -**available values**: -- `TCP` -- `UDP` - -**rules**: -- typeof `string` -- must match one of the pre-defined values - -### port - -Specifies the value of the `-p` flag. - -``` -Send the query to a non-standard port on the server, instead of the default port 53. -``` - -**key**: `measurementOptions.port` - -**default**: `53` - -**required**: `false` - -**rules**: -- typeof `number` - -```json - "port": 53 -``` - -### resolver - -Specifies the resolver server used for DNS lookup. - -``` -resolver is the name or IP address of the name server to query. This can be an IPv4 address in dotted-decimal [...]. When the supplied server argument is a hostname, dig resolves that name before querying that name server. -``` - -**key**: `measurementOptions.resolver` - -**required**: `false` - -**rules**: -- typeof `string` -- `FQDN` or `IPv4/noCIDR Address` - -```json - "resolver": "1.1.1.1" -``` -### trace - -Toggle tracing of the delegation path from the root name servers for the name being looked up. It will follow referrals from the root servers, showing the answer from each server that was used to resolve the lookup. - -**key**: `measurementOptions.trace` - -**required**: `false` - -**rules**: -- typeof `boolean` - -```json - "trace": true -``` - -

MTR

- -**type**: `mtr` - -Implementation of the native `mtr` command. - -mtr combines the functionality of the traceroute and ping programs in a single network diagnostic tool. - -example: -```json -{ - "type": "mtr", - "target": "google.com", - "measurementOptions": { - "protocol": "ICMP", - "port": 53, - "packets": 10 - }, - "locations": [], - "limit": 1 -} -``` - -### protocol - -Specifies the query protocol. - -**key**: `measurementOptions.protocol` - -**default**: `ICMP` - -**required**: `false` - -**available values**: -- `ICMP` (default) -- `TCP` -- `UDP` - -**rules**: -- typeof `string` -- must match one of the pre-defined values - -### port - -Specifies the value of the `-P` flag. - -``` -The target port number for TCP/SCTP/UDP traces. -``` - -**key**: `measurementOptions.port` - -**default**: `80` - -**required**: `false` - -**rules**: -- typeof `number` - -```json - "port": 53 -``` - -### packets - -Specifies the desired amount of `ECHO_REQUEST` packets to be sent. - -``` -Use this option to set the number of pings sent to determine both the machines on the network and the reliability of those machines. Each cycle lasts one second. -``` - -**key**: `measurementOptions.packets` - -**default**: `3` - -**required**: `false` - -**rules**: -- typeof `number` -- min `1` -- max `16` - -```json - "packets": 5 -``` - -

HTTP

- -**type**: `http` - -example: - -```json -{ - "type": "http", - "target": "jsdelivr.com", - "measurementOptions": { - "port": 443, - "protocol": "HTTPS", - "request": { - "path": "/", - "query": "?a=abc", - "method": "GET", - "host": "jsdelivr.com", - "headers": { - "Referer": "https://example.com/" - } - } - } -} -``` - -### path - -A URL pathname. - -**key**: `measurementOptions.request.path` - -**default**: `/` - -**required**: `false` - -**rules**: -- typeof `string` - -### query - -A query-string. - -**key**: `measurementOptions.request.query` - -**default**: `''` (empty string) - -**required**: `false` - -**rules**: -- typeof `string` - -## host - -Specifies the `Host` header, which is going to be added to the request. - -``` - Host: example.com -``` - -**key**: `measurementOptions.request.host` - -**default**: Host defined in `target` - -**required**: `false` - -**rules**: -- typeof `string` - -### method - -Specifies the HTTP method. - -**key**: `measurementOptions.request.method` - -**default**: `HEAD` - -**required**: `false` - -**available values**: -- `HEAD` (default) -- `GET` - -**rules**: -- typeof `string` -- must match one of the pre-defined values - -### headers - -**key**: `measurementOptions.request.headers` - -**default**: `{}` - -**required**: `false` - -**rules**: -- typeof `Object` -- key `User-Agent` is overridden -- key `Host` is overridden - -example: - -```json -{ - ... - "headers": { - "Referer": "https://example.com/" - } -} -``` - -### port - -**key**: `measurementOptions.port` - -**default**: `80` or `443` (depending on protocol) - -**required**: `false` - -**rules**: -- typeof `number` - -### protocol - -Specifies the query protocol. - -**key**: `measurementOptions.protocol` - -**default**: `HTTP` - -**required**: `false` - -**available values**: -- `HTTP` (default) -- `HTTPS` -- `HTTP2` - -**rules**: -- typeof `string` -- must match one of the pre-defined values - -### resolver - -Specifies the resolver server used for DNS lookup. - -**key**: `measurementOptions.resolver` - -**required**: `false` - -**rules**: -- typeof `string` -- `FQDN` or `IP Address` diff --git a/docs/measurement/schema/response.md b/docs/measurement/schema/response.md deleted file mode 100644 index 922f07e5..00000000 --- a/docs/measurement/schema/response.md +++ /dev/null @@ -1,934 +0,0 @@ -# Measurement Response Schema - -Jump to: -- [`Full success schema`](#success) -- [`Result schema`](#result) -- [`Ping Result schema`](#ping) -- [`Traceroute Result schema`](#traceroute) -- [`DNS Result schema`](#dns) -- [`MTR Result schema`](#mtr) -- [`HTTP Result schema`](#http) - -## Success - -### schema - -#### Id - -**key**: `id` - -**type**: `string` - -**mandatory**: `true` - -Measurement Id number, obtained from [`POST /v1/measurements`](../post-create.md) request. - -#### type - -**key**: `type` - -**type**: `string` - -**mandatory**: `true` - -The type of measurement. For more details about measurement type, please see [`MEASUREMENT REQUEST SCHEMA`](./request.md) - -**available values**: -- `ping` -- `dns` -- `traceroute` -- `mtr` - -#### status - -**key**: `status` - -**type**: `string` - -**mandatory**: `true` - -Measurement's current status. Communication between server and probes is asynchronious, thus the response should not be considered final, until this field returns `finished`. - -**available values**: -- `finished` -- `in-progress` - -#### createdAt / updatedAt - -**key**: `createdAt` `updatedAt` - -**type**: `string` (ISO) - -**mandatory**: `true` - -#### target - -**key**: `target` - -**type**: `string` - -**mandatory**: `true` - -An endpoint on which the tests were executed. For more details about measurement target, please see [`MEASUREMENT REQUEST SCHEMA`](./request.md) - -#### limit - -**key**: `limit` - -**type**: `number` - -**mandatory**: `false` - -The global limit of probes that was specified in the request body. For more details about measurement limit, please see [`MEASUREMENT REQUEST SCHEMA`](./request.md) - -#### probesCount - -**key**: `probesCount` - -**type**: `number` - -**mandatory**: `true` - -The number of probes that performed the measurement. `probesCount` is always smaller or equal to `limit`. - -#### locations - -**key**: `locations` - -**type**: `Location[]` - -**mandatory**: `false` - -A list of desired locations from which tests were executed. For more details about measurement locations, please see [`MEASUREMENT REQUEST SCHEMA`](./request.md) - -#### measurementOptions - -**key**: `measurementOptions` - -**type**: `Object` - -**mandatory**: `false` - -Measurement options that were specified during measurement request. For more details about measurement options, please see [`MEASUREMENT REQUEST SCHEMA`](./request.md) - -#### results[] - -**key**: `results` - -**type**: `Object[]` - -**mandatory**: `true` - -An array of all probe responses. Jump to [`RESULT SCHEMA`](#result) for more details. - -### example - -```json -{ - "id": "tEaUg3vYnOu2exVC", - "type": "ping", - "status": "finished", - "createdAt": "2022-07-17T16:19:52.909Z", - "updatedAt": "2022-07-17T16:19:52.909Z", - "results": [ - { - "probe": { - "continent": "AF", - "region": "southern africa", - "country": "ZA", - "state": null, - "city": "cape town", - "asn": 16509, - "longitude": 18.4232, - "latitude": -33.9258, - "network": "amazon.com inc.", - "tags": [] - }, - "result": { - "status": "finished", - "resolvedAddress": "172.217.170.14", - "resolvedHostname": "lhr25s33-in-f14.1e100.net", - "timings": [ - { - "ttl": 108, - "rtt": 16.5 - }, - { - "ttl": 108, - "rtt": 16.5 - }, - { - "ttl": 108, - "rtt": 16.5 - } - ], - "stats": { - "min": 16.474, - "avg": 16.504, - "max": 16.543, - "loss": 0, - }, - "rawOutput": "PING google.com (172.217.170.14) 56(84) bytes of data.\n64 bytes from lhr25s33-in-f14.1e100.net (172.217.170.14): icmp_seq=1 ttl=108 time=16.5 ms\n64 bytes from lhr25s33-in-f14.1e100.net (172.217.170.14): icmp_seq=2 ttl=108 time=16.5 ms\n64 bytes from lhr25s33-in-f14.1e100.net (172.217.170.14): icmp_seq=3 ttl=108 time=16.5 ms\n\n--- google.com ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 402ms\nrtt min/avg/max/mdev = 16.474/16.504/16.543/0.028 ms" - } - } - ] -} -``` -## Result - -possible result types: -- [`ping`](#ping) -- [`traceroute`](#traceroute) -- [`dns`](#dns) -- [`mtr`](#mtr) -- [`http`](#http) - -### shared values - -#### Result.probe - -**key**: `Result.probe` - -**type**: `Object` - -Probe metadata, containing its precise geo location. - -#### status - -**key**: `Result.result.status` - -**type**: `string` - -Measurement's current status for the specific probe. Communication between server and probe is asynchronious, thus the response should not be considered final, until this field returns `finished` or `failed`. - -**available values**: -- `in-progress` -- `finished` -- `failed` - -#### rawOutput - -**key**: `Result.result.rawOutput` - -**type**: `string` - -The raw, unparsed stdout output from the native command. - -### PING - -#### resolvedAddress - -**key**: Result.result.resolvedAddress` - -**type**: `string` - -IP Address contained within `ping` response header. - -#### resolvedHostname - -**key**: Result.result.resolvedHostname` - -**type**: `string` - -Hostname address contained within `ping` response header. - -#### stats - -A container object for test stats. - -**key**: `Result.result.stats` - -**type**: `Object` - -#### stats.loss - -**key**: `Result.result.stats.loss` - -**type**: `number` - -total count of lost packets. - -#### stats.min / stats.avg / stats.max - -**key**: `Result.result.stats.min` `Result.result.stats.avg` `Result.result.stats.max` - -**type**: `number` - -stats in millisecond contained within `ping` response footer. - -#### timings[] - -**key**: `Result.result.timings[]` - -**type**: `Object[]` - -An array of all PING iterations before the deadline occurred. - -#### timings[].ttl - -**key**: `Result.result.timings[].ttl` - -**type**: `number` - -#### timings[].time - -**key**: `Result.result.timings[].time` - -**type**: `number` - -### TRACEROUTE - -#### resolvedAddress - -**key**: Result.result.resolvedAddress` - -**type**: `string` - -IP Address contained within `traceroute` response header. - -#### resolvedHostname - -**key**: Result.result.resolvedHostname` - -**type**: `string` - -Hostname address contained within `traceroute` response header. - -#### hops[] - -**key**: `Result.result.hops[]` - -**type**: `Object[]` - -An array of all traceroute iterations before the deadline occurred. - -#### hops[].resolvedHostname - -**key**: `Result.result.hops[].resolvedHostname` - -**type**: `string` - -reported hostname. - -#### hops[].resolvedAddress - -**key**: `Result.result.hops[].resolvedAddress` - -**type**: `string` - -reported ip address. - -#### hops[].timings[] - -**key**: `Result.result.hops[].timings[]` - -**type**: `Object[]` - -#### hops[].timings[].rtt - -**key**: `Result.result.times[].rtt` - -**type**: `number` - -the delay between sending the packet and getting the response. - -### DNS - -***warning!***: In case the measurement was requested with `trace` enabled, the `Result.hops` will be of type `Object[]`. This is because each trace path is returned individually by the probe. - -example: -```json -{ - "id": "RelHRYGX9yAYtI0b", - "type": "dns", - "status": "finished", - "createdAt": "2022-07-17T16:19:52.909Z", - "updatedAt": "2022-07-17T16:19:52.909Z", - "results": [ - { - "probe": { - "continent": "EU", - "region": "western europe", - "country": "NL", - "state": null, - "city": "naarden", - "asn": 33915, - "longitude": 5.1563, - "latitude": 52.285, - "network": "ziggo", - "tags": [] - }, - "result": { - "hops": [ - { - "answers": [ - { - "name": ".", - "type": "NS", - "ttl": 362800, - "class": "IN", - "value": "a.root-servers.net." - }, - ... - { - "name": ".", - "type": "NS", - "ttl": 362800, - "class": "IN", - "value": "m.root-servers.net." - } - ], - "timings": { - "total": 0 - }, - "resolver": "192.168.0.49" - }, - ... - { - "answers": [ - { - "name": "google.com.", - "type": "A", - "ttl": 300, - "class": "IN", - "value": "142.250.178.14" - } - ], - "timings": { - "total": 28 - }, - "resolver": "ns1.google.com" - } - ], - "rawOutput": "\n; <<>> DiG 9.16.1-Ubuntu <<>> google.com -t A -p 53 -4 +timeout=3 +tries=2 +nocookie +trace\n;; global options: +cmd\n.\t\t\t362800\tIN\tNS\ta.root-servers.net.\n.\t\t\t362800\tIN\tNS\tb.root-servers.net.\n.\t\t\t362800\tIN\tNS\tc.root-servers.net.\n.\t\t\t362800\tIN\tNS\td.root-servers.net.\n.\t\t\t362800\tIN\tNS\te.root-servers.net.\n.\t\t\t362800\tIN\tNS\tf.root-servers.net.\n.\t\t\t362800\tIN\tNS\tg.root-servers.net.\n.\t\t\t362800\tIN\tNS\th.root-servers.net.\n.\t\t\t362800\tIN\tNS\ti.root-servers.net.\n.\t\t\t362800\tIN\tNS\tj.root-servers.net.\n.\t\t\t362800\tIN\tNS\tk.root-servers.net.\n.\t\t\t362800\tIN\tNS\tl.root-servers.net.\n.\t\t\t362800\tIN\tNS\tm.root-servers.net.\n;; Received 492 bytes from 192.168.0.49#53(192.168.0.49) in 0 ms\n\ncom.\t\t\t172800\tIN\tNS\te.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\tb.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\tj.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\tm.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\ti.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\tf.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\ta.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\tg.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\th.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\tl.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\tk.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\tc.gtld-servers.net.\ncom.\t\t\t172800\tIN\tNS\td.gtld-servers.net.\ncom.\t\t\t86400\tIN\tDS\t30909 8 2 E2D3C916F6DEEAC73294E8268FB5885044A833FC5459588F4A9184CF C41A5766\ncom.\t\t\t86400\tIN\tRRSIG\tDS 8 1 86400 20220730050000 20220717040000 20826 . EioIIaaJd6MrRl24dvoN/uEZeTC99lAcUkOw8N2V0xYLvpzgjEqCTrav NL/hsoesQaqV4oEHAhaGLN3CqeD67/H3486F/qvsgzUr44+A3snPuSEY MII2V1pvqpoSS/CRFp/WeocdKNLvG8mJ3ZAj5940WkZz7kctpvjuUTU3 mrlNbNZ5L711Jfg+7cClnEwH4S0zYXIO2GKQ4ODJNu6AGqXJmnhAV/Kr V9eqgnlN0Jephc2yPyQOsuDLEaZktgQDKyJZdhVXOaKPyJ2kXjiEP54M jHPJSKsnhvm5Yzp3yZ0vs1/nUplwtot4EmBEspr8IURoopRmBKgTrpR7 jNBobg==\n;; Received 1170 bytes from 198.41.0.4#53(a.root-servers.net) in 16 ms\n\ngoogle.com.\t\t172800\tIN\tNS\tns2.google.com.\ngoogle.com.\t\t172800\tIN\tNS\tns1.google.com.\ngoogle.com.\t\t172800\tIN\tNS\tns3.google.com.\ngoogle.com.\t\t172800\tIN\tNS\tns4.google.com.\nCK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN NSEC3 1 1 0 - CK0Q2D6NI4I7EQH8NA30NS61O48UL8G5 NS SOA RRSIG DNSKEY NSEC3PARAM\nCK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN RRSIG NSEC3 8 2 86400 20220722042351 20220715031351 37269 com. CGDG1OnS2M4TfifTOQPoDPkONkkSyrgg/J0R9YJMr2DoNwWKDtAlkFCC w6Zudqnhww+ueLqL9JMXgveLaD9yz+DVWUG0bVQrjwXeOETE77TP2o1n Y0gI+grgHq2RnbCQQYwrQCP+y5uW60umtQjNJ5Jrn79GcF71310gEyqP ssKDXc+larXUFTX4ioVjhaNJmxAJ+1HMNOEx0mExixJSfA==\nS84BKCIBC38P58340AKVNFN5KR9O59QC.com. 86400 IN NSEC3 1 1 0 - S84BUO64GQCVN69RJFUO6LVC7FSLUNJ5 NS DS RRSIG\nS84BKCIBC38P58340AKVNFN5KR9O59QC.com. 86400 IN RRSIG NSEC3 8 2 86400 20220723051421 20220716040421 32298 com. Sa83wfjljiKv2sIWRuo38q0SO8Awm6Qeb1b5imsCWJFtKGi0O9peJOZH yeGkl83IhLXdNNpxIHDl4FylvNHtUK1lLouSLZ6mEArRSTHftmyVsCX4 QXnmgIRsnYxNkdT73ROC0XJKwDY7yugRu+atn3sxKgWT2Ix6akhxVYfZ 0hATnASS1+vgRlgI577xRFkwaz2bKk/uq6P+PghDiSox+Q==\n;; Received 836 bytes from 192.26.92.30#53(c.gtld-servers.net) in 104 ms\n\ngoogle.com.\t\t300\tIN\tA\t142.250.178.14\n;; Received 55 bytes from 216.239.32.10#53(ns1.google.com) in 28 ms\n" - } - } - ]} -} -``` - -#### resolver - -**key**: `Result.result.resolver` - -**type**: `string` - -IP Address of the resolver used. - -#### timings - -**key**: `Result.result.timings` - -**type**: `Object` - -stats container object, containing all timings details. - -#### timings.total - -**key**: `Result.result.timings.total` - -**type**: `number` - -time it took to complete the request. Reported by `dig`. - -#### answers[] - -**key**: `Result.result.answers[]` - -**type**: `Object[]` - -An array of all returned DNS results. - -#### answer[].name - -**key**: `Result.result.answer[].name` - -**type**: `string` - -#### answer[].value - -**key**: `Result.result.answer[].value` - -**type**: `string` - -reported record's value. Depending on type, it might be an IP Address, or plain text. - -#### answer[].type - -**key**: `Result.result.answer[].type` - -**type**: `string` - -record type. - -#### answer[].ttl - -**key**: `Result.result.answer[].ttl` - -**type**: `string` - -record ttl. - -#### answer[].class - -**key**: `Result.result.answer[].class` - -**type**: `string` - -record class code. - -### MTR - -example: - -```json -{ - "id": "aAizKkYaSuXyUx0r", - "type": "mtr", - "status": "finished", - "createdAt": "2022-07-17T16:19:52.909Z", - "updatedAt": "2022-07-17T16:19:52.909Z", - "results": - [ - { - "probe": - { - "continent": "AF", - "region": "western africa", - "country": "NG", - "state": null, - "city": "lagos", - "asn": 56655, - "longitude": 3.41, - "latitude": 6.49, - "network": "terrahost", - "tags": [] - }, - "result": - { - "hops": - [ - { - "stats": - { - "min": 0.199, - "max": 0.294, - "avg": 0.2, - "total": 3, - "rcv": 3, - "drop": 0, - "stDev": 0.1, - "jMin": 0.1, - "jMax": 0.2, - "jAvg": 0.2 - }, - "timings": - [ - { - "rtt": 0.294 - }, - { - "rtt": 0.199 - }, - { - "rtt": 0.227 - } - ], - "duplicate": false, - "asn": [56655], - "resolvedAddress": "195.16.73.1", - "resolvedHostname": "static.195.16.73.1.terrahost.com" - }, - ... - ], - "rawOutput": "Host Loss% Drop Rcv Avg StDev Javg \n 1. AS56655 _gateway (195.16.73.1) 0.0% 0 3 0.2 0.1 0.2\n 2. AS??? 196.216.148.25 (196.216.148.25) 0.0% 0 3 1.0 0.1 0.5\n 3. AS15169 108.170.240.34 (108.170.240.34) 0.0% 0 3 1.3 0.1 0.6\n 4. AS15169 142.251.50.69 (142.251.50.69) 0.0% 0 3 76.7 0.1 38.4\n 5. AS15169 142.251.226.177 (142.251.226.177) 0.0% 0 3 95.2 0.4 47.7\n 6. AS15169 142.251.232.224 (142.251.232.224) 0.0% 0 3 104.7 0.1 52.3\n 7. AS15169 142.251.232.223 (142.251.232.223) 0.0% 0 3 105.8 0.0 52.9\n 8. AS15169 74.125.242.97 (74.125.242.97) 0.0% 0 3 102.8 0.0 51.4\n 9. AS15169 108.170.234.231 (108.170.234.231) 0.0% 0 3 101.7 0.1 50.9\n10. AS15169 lhr48s29-in-f14.1e100.net (142.250.200.14) 0.0% 0 3 102.0 0.0 51.0\n", - - } - } - ] -} -``` - -#### hops[] - -**key**: `Result.result.hops[]` - -**type**: `Object[]` - -An array of all returned MTR results. - -#### hops[].resolvedHostname - -**key**: `Result.result.hops[].resolvedHostname` - -**type**: `string` - -#### hops[].resolvedAddress - -**key**: `Result.result.hops[].resolvedAddress` - -**type**: `string` - -#### hops[].asn - -**key**: `Result.result.hops[].asn` - -**type**: `number[]` - -An array, containing AS numbers assigned to this IP Address. - -#### hops[].timings[] - -**key**: `Result.result.hops[].timings[]` - -**type**: `Object[]` - -An array of all ping records. - -#### hops[].timings[].rtt - -**key**: `Result.result.hops[].timings[].rtt` - -**type**: `number | null` - -Ping response time. - -#### hops[].stats - -**key**: `Result.result.hops[].stats` - -**type**: `Object` - -A stats summary of ping responses. - -#### hops[].stats.min - -**key**: `Result.result.hops[].stats.min` - -**type**: `float` - -The lowest response time. - -#### hops[].stats.max - -**key**: `Result.result.hops[].stats.max` - -**type**: `float` - -The longest response time. - -#### hops[].stats.avg - -**key**: `Result.result.hops[].stats.avg` - -**type**: `float` - -The average response time. - -#### hops[].stats.total - -**key**: `Result.result.hops[].stats.total` - -**type**: `number` - -The total count of PING packets sent. - -#### hops[].stats.rcv - -**key**: `Result.result.hops[].stats.rcv` - -**type**: `number` - -The total count of PING responses. - -#### hops[].stats.drop - -**key**: `Result.result.hops[].stats.drop` - -**type**: `number` - -The total count of PING packets, to which response never came. - -#### hops[].stats.stDev - -**key**: `Result.result.hops[].stats.stDev` - -**type**: `float` - -standard deviation - -#### hops[].stats.jMin - -**key**: `Result.result.hops[].stats.jMin` - -**type**: `float` - -The lowest jitter between response times. - -#### hops[].stats.jMax - -**key**: `Result.result.hops[].stats.jMax` - -**type**: `float` - -The largest jitter between response times. - -#### hops[].stats.jAvg - -**key**: `Result.result.hops[].stats.jAvg` - -**type**: `float` - -The average jitter between response times. - -### HTTP - -example: - -```json -{ - "id": "EXrIBBoduJUCcJRa", - "type": "http", - "status": "finished", - "createdAt": 1658480890820, - "updatedAt": 1658480891219, - "results": [ - { - "probe": { - "continent": "NA", - "region": "northern america", - "country": "CA", - "state": null, - "city": "toronto", - "asn": 396982, - "longitude": -79.4163, - "latitude": 43.7001, - "network": "google cloud", - "tags": [], - }, - "result": { - "resolvedAddress": "142.250.178.14", - "headers": { - "location": "https://www.google.com/", - "content-type": "text/html; charset=UTF-8", - "date": "Fri, 22 Jul 2022 09:08:11 GMT", - "expires": "Sun, 21 Aug 2022 09:08:11 GMT", - "cache-control": "public, max-age=2592000", - "server": "gws", - "content-length": "220", - "x-xss-protection": "0", - "x-frame-options": "SAMEORIGIN", - "connection": "close" - }, - "rawHeaders": "Location: https://www.google.com/\nContent-Type: text/html; charset=UTF-8\nDate: Fri, 22 Jul 2022 09:08:11 GMT\nExpires: Sun, 21 Aug 2022 09:08:11 GMT\nCache-Control: public, max-age=2592000\nServer: gws\nContent-Length: 220\nX-XSS-Protection: 0\nX-Frame-Options: SAMEORIGIN\nConnection: close", - "rawBody": "", - "statusCode": 301, - "timings": { - "firstByte": 131, - "dns": 3, - "tls": 27, - "tcp": 1, - "total": 163, - "download": 1 - }, - "tls": { - "authorized": true, - "createdAt": "Jun 27 08:17:39 2022 GMT", - "expireAt": "Sep 19 08:17:38 2022 GMT", - "issuer": { - "C": "US", - "O": "Google Trust Services LLC", - "CN": "GTS CA 1C3" - }, - "subject": { - "CN": "*.google.com", - "alt": "DNS:*.google.com, DNS:*.appengine.google.com, DNS:*.bdn.dev, DNS:*.cloud.google.com, DNS:*.crowdsource.google.com, DNS:*.datacompute.google.com, DNS:*.google.ca, DNS:*.google.cl, DNS:*.google.co.in, DNS:*.google.co.jp, DNS:*.google.co.uk, DNS:*.google.com.ar, DNS:*.google.com.au, DNS:*.google.com.br, DNS:*.google.com.co, DNS:*.google.com.mx, DNS:*.google.com.tr, DNS:*.google.com.vn, DNS:*.google.de, DNS:*.google.es, DNS:*.google.fr, DNS:*.google.hu, DNS:*.google.it, DNS:*.google.nl, DNS:*.google.pl, DNS:*.google.pt, DNS:*.googleadapis.com, DNS:*.googleapis.cn, DNS:*.googlevideo.com, DNS:*.gstatic.cn, DNS:*.gstatic-cn.com, DNS:googlecnapps.cn, DNS:*.googlecnapps.cn, DNS:googleapps-cn.com, DNS:*.googleapps-cn.com, DNS:gkecnapps.cn, DNS:*.gkecnapps.cn, DNS:googledownloads.cn, DNS:*.googledownloads.cn, DNS:recaptcha.net.cn, DNS:*.recaptcha.net.cn, DNS:recaptcha-cn.net, DNS:*.recaptcha-cn.net, DNS:widevine.cn, DNS:*.widevine.cn, DNS:ampproject.org.cn, DNS:*.ampproject.org.cn, DNS:ampproject.net.cn, DNS:*.ampproject.net.cn, DNS:google-analytics-cn.com, DNS:*.google-analytics-cn.com, DNS:googleadservices-cn.com, DNS:*.googleadservices-cn.com, DNS:googlevads-cn.com, DNS:*.googlevads-cn.com, DNS:googleapis-cn.com, DNS:*.googleapis-cn.com, DNS:googleoptimize-cn.com, DNS:*.googleoptimize-cn.com, DNS:doubleclick-cn.net, DNS:*.doubleclick-cn.net, DNS:*.fls.doubleclick-cn.net, DNS:*.g.doubleclick-cn.net, DNS:doubleclick.cn, DNS:*.doubleclick.cn, DNS:*.fls.doubleclick.cn, DNS:*.g.doubleclick.cn, DNS:dartsearch-cn.net, DNS:*.dartsearch-cn.net, DNS:googletraveladservices-cn.com, DNS:*.googletraveladservices-cn.com, DNS:googletagservices-cn.com, DNS:*.googletagservices-cn.com, DNS:googletagmanager-cn.com, DNS:*.googletagmanager-cn.com, DNS:googlesyndication-cn.com, DNS:*.googlesyndication-cn.com, DNS:*.safeframe.googlesyndication-cn.com, DNS:app-measurement-cn.com, DNS:*.app-measurement-cn.com, DNS:gvt1-cn.com, DNS:*.gvt1-cn.com, DNS:gvt2-cn.com, DNS:*.gvt2-cn.com, DNS:2mdn-cn.net, DNS:*.2mdn-cn.net, DNS:googleflights-cn.net, DNS:*.googleflights-cn.net, DNS:admob-cn.com, DNS:*.admob-cn.com, DNS:*.gstatic.com, DNS:*.metric.gstatic.com, DNS:*.gvt1.com, DNS:*.gcpcdn.gvt1.com, DNS:*.gvt2.com, DNS:*.gcp.gvt2.com, DNS:*.url.google.com, DNS:*.youtube-nocookie.com, DNS:*.ytimg.com, DNS:android.com, DNS:*.android.com, DNS:*.flash.android.com, DNS:g.cn, DNS:*.g.cn, DNS:g.co, DNS:*.g.co, DNS:goo.gl, DNS:www.goo.gl, DNS:google-analytics.com, DNS:*.google-analytics.com, DNS:google.com, DNS:googlecommerce.com, DNS:*.googlecommerce.com, DNS:ggpht.cn, DNS:*.ggpht.cn, DNS:urchin.com, DNS:*.urchin.com, DNS:youtu.be, DNS:youtube.com, DNS:*.youtube.com, DNS:youtubeeducation.com, DNS:*.youtubeeducation.com, DNS:youtubekids.com, DNS:*.youtubekids.com, DNS:yt.be, DNS:*.yt.be, DNS:android.clients.google.com, DNS:developer.android.google.cn, DNS:developers.android.google.cn, DNS:source.android.google.cn" - } - }, - "rawOutput": "HTTP/1.1 301\nLocation: https://www.google.com/\nContent-Type: text/html; charset=UTF-8\nDate: Fri, 22 Jul 2022 09:08:11 GMT\nExpires: Sun, 21 Aug 2022 09:08:11 GMT\nCache-Control: public, max-age=2592000\nServer: gws\nContent-Length: 220\nX-XSS-Protection: 0\nX-Frame-Options: SAMEORIGIN\nConnection: close" - } - } - ] -} -``` -#### resolvedAddress - -**key**: `Result.result.resolvedAddress` - -**type**: `string` - -#### rawHeaders - -**key**: `Result.result.rawHeaders` - -**type**: `string` - -Unparsed response headers. - -#### Headers - -**key**: `Result.result.headers` - -**type**: `Object` - -JSON parsed resposne headers. - -#### rawBody - -**key**: `Result.result.rawBody` - -**type**: `string` - -Unparsed response body. - -#### statusCode - -**key**: `Result.result.statusCode` - -**type**: `integer` - -#### timings - -**key**: `Result.result.timings` - -**type**: `Object` - -A full breakdown of how long it took to complete the request. - -#### timings.firstByte / timings.dns / timings.tls / timings.tcp / timings.download / timings.total - -**key**: -`Result.result.timings.firstByte` `Result.result.timings.dns` `Result.result.timings.tls` `Result.result.timings.tcp` `Result.result.timings.download` `Result.result.timings.total` - -**type**: `integer` - -**warning**: -HTTP2 requests might be lacknig `dns`, `tls` and `tcp` data. - -#### tls - -**key**: `Result.result.tls` - -**type**: `Object` - -#### tls.authorized - -**key**: `Result.result.tls.authorized` - -**type**: `boolean` - -#### tls.authorizationError - -**key**: `Result.result.tls.authorizationError` - -**type**: `string` - -An error message describing the unsuccessful TLS authorization. - -#### tls.createdAt / tls.updatedAt - -**key**: `Result.result.tls.createdAt` `Result.result.tls.updatedAt` - -**type**: `string` (ISO) - -#### issuer - -**key**: `Result.result.tls.issuer` - -**type**: `Object` - -#### issuer.C - -**key**: `Result.result.tls.issuer.C` - -**type**: `string` - -Issuer's registration country - -#### issuer.ST - -**key**: `Result.result.tls.issuer.ST` - -**type**: `string` - -Issuer's registration state (US) - -#### issuer.L - -**key**: `Result.result.tls.issuer.L` - -**type**: `string` - -Issuer's registration city - -#### issuer.O - -**key**: `Result.result.tls.issuer.O` - -**type**: `string` - -Issuer's registration organization - -#### issuer.CN - -**key**: `Result.result.tls.issuer.CN` - -**type**: `string` - -Issuer's organization common name - -#### subject - -**key**: `Result.result.tls.suject` - -**type**: `Object` - -#### subject.C - -**key**: `Result.result.tls.suject.C` - -**type**: `string` - -Subject's registration country - -#### subject.ST - -**key**: `Result.result.tls.suject.ST` - -**type**: `string` - -Subject's registration state (US) - -#### subject.L - -**key**: `Result.result.tls.suject.L` - -**type**: `string` - -Subject's registration city - -#### subjects.O - -**key**: `Result.result.tls.subject.O` - -**type**: `string` - -Subjects's registration organization - - -#### subject.CN - -**key**: `Result.result.tls.subject.CN` - -**type**: `string` - -Subject's name (FQDN) - -#### subject.alt - -**key**: `Result.result.tls.subject.alt` - -**type**: `string` - -A list of alternative names (FQDN) associated with this certificate. Each record is prefixed with `DNS:`, and seperated with comma (`,`). - diff --git a/docs/probes/get.md b/docs/probes/get.md deleted file mode 100644 index 03b62efe..00000000 --- a/docs/probes/get.md +++ /dev/null @@ -1,95 +0,0 @@ -# get probes - -Get list of all probes currently online and connected to the API server. - -## request - -**method**: `GET` - -**url**: `/v1/probes` - -## success response - -**status code**: `200 OK` - -### schema - -``` -[ - { - version: string - location: { - continent: string - region: string - country: string - state?: string - city: string - asn: number - latitude: decimal - longitude: decimal - network: string - } - tags: string[] - resolvers: string[] - } -] -``` - -### example - -```json -GET https://api.globalping.io/v1/probes -[ - { - "version": "0.10.1", - "location": { - "continent": "EU", - "region": "Western Europe", - "country": "BE", - "city": "Brussels", - "asn": 396982, - "latitude": 50.8505, - "longitude": 4.3488, - "network": "Google LLC" - }, - "tags": [ - "gcp-europe-west1" - ], - "resolvers": [ - "private" - ] - }, - { - "version": "0.10.1", - "location": { - "continent": "EU", - "region": "Northern Europe", - "country": "IE", - "city": "Dublin", - "asn": 16509, - "latitude": 53.3331, - "longitude": -6.2489, - "network": "Amazon.com, Inc." - }, - "tags": [ - "aws-eu-west-1" - ], - "resolvers": [ - "private" - ] - } -] -``` - -## error response (500) - -**status code**: `500 Internal Server Error` - -```json -{ - "error": { - "message": "Internal Server Error", - "type": "api_error" - } -} -``` diff --git a/public/v1/spec.yaml b/public/v1/spec.yaml index c0c45f9b..50d68729 100644 --- a/public/v1/spec.yaml +++ b/public/v1/spec.yaml @@ -40,6 +40,8 @@ paths: operationId: createMeasurement description: | Creates a new measurement with the configured parameters. + The measurement runs asynchronously and its current state can be retrieved + at the URL returned in the `Location` header. ### Client guidelines @@ -163,7 +165,7 @@ components: id: type: string probesCount: - type: integer + $ref: '#/components/schemas/MeasurementProbesCount' Latitude: type: number Longitude: @@ -546,6 +548,9 @@ components: - HTTPS - HTTP2 default: HTTPS + MeasurementProbesCount: + type: integer + description: The number of probes that performed the measurement. Smaller or equal to `limit`. MeasurementResolver: description: A DNS resolver to use for the query. Defaults to the probe's system resolver. anyOf: @@ -651,8 +656,7 @@ components: format: date-time description: Time when the measurement was last updated. probesCount: - type: integer - description: The number of probes that performed the measurement. Smaller or equal to `limit`. + $ref: '#/components/schemas/MeasurementProbesCount' locations: allOf: - $ref: '#/components/schemas/MeasurementLocations' diff --git a/src/health/route/get.ts b/src/health/route/get.ts index 49d32199..510ab00a 100644 --- a/src/health/route/get.ts +++ b/src/health/route/get.ts @@ -9,5 +9,5 @@ const handle = (ctx: ParameterizedContext { - router.get('/health', handle); + router.get('/health', '/health', handle); }; diff --git a/src/lib/http/middleware/default-json.ts b/src/lib/http/middleware/default-json.ts index c8c2a072..47fd1b0d 100644 --- a/src/lib/http/middleware/default-json.ts +++ b/src/lib/http/middleware/default-json.ts @@ -1,8 +1,8 @@ -import type { Context, Next } from 'koa'; +import type { ExtendedMiddleware } from '../../../types.js'; import createHttpError from 'http-errors'; import _ from 'lodash'; -export const defaultJson = () => async (ctx: Context, next: Next) => { +export const defaultJson = (): ExtendedMiddleware => async (ctx, next) => { await next(); if (ctx.status >= 400 && !ctx.body) { @@ -13,6 +13,9 @@ export const defaultJson = () => async (ctx: Context, next: Next) => { type: _.snakeCase(error.message), message: `${error.message}.`, }, + links: { + documentation: ctx.getDocsLink(), + }, }; } }; diff --git a/src/lib/http/middleware/docs-link.ts b/src/lib/http/middleware/docs-link.ts new file mode 100644 index 00000000..e0c35e24 --- /dev/null +++ b/src/lib/http/middleware/docs-link.ts @@ -0,0 +1,28 @@ +import type Koa from 'koa'; +import type Router from '@koa/router'; + +export const docsLink = (options: DocsLinkOptions): DocsLinkMiddleware => async (ctx, next) => { + ctx.getDocsLink = (routeName = ctx._matchedRouteName as string, method = ctx.method === 'HEAD' ? 'GET' : ctx.method) => { + return `${options.docsHost}/docs/api.globalping.io${getDocsPath(ctx.router, routeName, method)}`; + }; + + return next(); +}; + +const getDocsPath = (router: Router, routeName: string | undefined, method: string) => { + if (!routeName || routeName === '/') { + return ''; + } + + const route = router.route(routeName); + + if (typeof route !== 'object' || !route.name) { + throw new Error(`Unknown route ${routeName}.`); + } + + return `#${method.toLowerCase()}-/v1${route.name.replace(/:(\w+)/g, '-$1-')}`; +}; + +export type DocsLinkOptions = { docsHost: string }; +export type DocsLinkContext = { getDocsLink(routeName?: string, method?: string): string }; +export type DocsLinkMiddleware = Router.Middleware; diff --git a/src/lib/http/middleware/error-handler.ts b/src/lib/http/middleware/error-handler.ts index b735e82e..2a835cea 100644 --- a/src/lib/http/middleware/error-handler.ts +++ b/src/lib/http/middleware/error-handler.ts @@ -1,11 +1,11 @@ -import type { Context, Next } from 'koa'; import createHttpError from 'http-errors'; import newrelic from 'newrelic'; import { scopedLogger } from '../../logger.js'; +import type { ExtendedMiddleware } from '../../../types'; const logger = scopedLogger('error-handler-mw'); -export const errorHandlerMw = async (ctx: Context, next: Next) => { +export const errorHandlerMw: ExtendedMiddleware = async (ctx, next) => { try { await next(); } catch (error: unknown) { @@ -17,6 +17,9 @@ export const errorHandlerMw = async (ctx: Context, next: Next) => { type: error['type'] as string ?? 'api_error', message: error.expose ? error.message : `${createHttpError(error.status).message}.`, }, + links: { + documentation: ctx.getDocsLink(), + }, }; return; @@ -35,6 +38,9 @@ export const errorHandlerMw = async (ctx: Context, next: Next) => { type: 'api_error', message: 'Internal Server Error.', }, + links: { + documentation: ctx.getDocsLink(), + }, }; } }; diff --git a/src/lib/http/middleware/validate.ts b/src/lib/http/middleware/validate.ts index 21f10b86..4601aab2 100644 --- a/src/lib/http/middleware/validate.ts +++ b/src/lib/http/middleware/validate.ts @@ -1,7 +1,7 @@ import type { Schema } from 'joi'; -import type { Context, Next } from 'koa'; +import type { ExtendedMiddleware } from '../../../types.js'; -export const validate = (schema: Schema) => async (ctx: Context, next: Next) => { +export const validate = (schema: Schema): ExtendedMiddleware => async (ctx, next) => { const valid = schema.validate(ctx.request.body, { convert: true }); if (valid.error) { @@ -15,6 +15,9 @@ export const validate = (schema: Schema) => async (ctx: Context, next: Next) => message: 'Parameter validation failed.', params: Object.fromEntries(fields) as never, }, + links: { + documentation: ctx.getDocsLink(), + }, }; return; diff --git a/src/lib/http/server.ts b/src/lib/http/server.ts index 0a4058b2..77b719d1 100644 --- a/src/lib/http/server.ts +++ b/src/lib/http/server.ts @@ -9,6 +9,7 @@ import etag from 'koa-etag'; import responseTime from 'koa-response-time'; import koaFavicon from 'koa-favicon'; import koaStatic from 'koa-static'; +import config from 'config'; import cjsDependencies from '../../cjs-dependencies.cjs'; import { registerGetProbesRoute } from '../../probe/route/get-probes.js'; import { registerGetMeasurementRoute } from '../../measurement/route/get-measurement.js'; @@ -20,20 +21,23 @@ import { errorHandlerMw } from './middleware/error-handler.js'; import { corsHandler } from './middleware/cors.js'; import { isAdminMw } from './middleware/is-admin.js'; import domainRedirect from './middleware/domain-redirect.js'; +import { docsLink } from './middleware/docs-link.js'; +import type { CustomContext } from '../../types.js'; const app = new cjsDependencies.Koa(); const publicPath = url.fileURLToPath(new URL('.', import.meta.url)) + '/../../../public'; +const docsHost = config.get('server.docsHost'); const rootRouter = new Router({ strict: true, sensitive: true }); rootRouter.prefix('/'); // GET / -rootRouter.get('/', (ctx) => { +rootRouter.get('/', '/', (ctx) => { ctx.status = 404; ctx.body = { links: { - documentation: 'https://github.com/jsdelivr/globalping/tree/master/docs', + documentation: ctx.getDocsLink(), }, }; }); @@ -62,6 +66,7 @@ app .use(conditionalGet()) .use(etag({ weak: true })) .use(json({ pretty: true, spaces: 2 })) + .use(docsLink({ docsHost })) .use(defaultJson()) // Error handler must always be the first middleware in a chain unless you know what you are doing ;) .use(errorHandlerMw) diff --git a/src/measurement/route/create-measurement.ts b/src/measurement/route/create-measurement.ts index 6f66adad..6084108c 100644 --- a/src/measurement/route/create-measurement.ts +++ b/src/measurement/route/create-measurement.ts @@ -25,5 +25,5 @@ const handle = async (ctx: Context): Promise => { }; export const registerCreateMeasurementRoute = (router: Router): void => { - router.post('/measurements', bodyParser(), validate(schema), rateLimitHandler(), handle); + router.post('/measurements', '/measurements', bodyParser(), validate(schema), rateLimitHandler(), handle); }; diff --git a/src/measurement/route/get-measurement.ts b/src/measurement/route/get-measurement.ts index 82835e3b..143a3e41 100644 --- a/src/measurement/route/get-measurement.ts +++ b/src/measurement/route/get-measurement.ts @@ -24,5 +24,5 @@ const handle = async (ctx: ParameterizedContext { - router.get('/measurements/:id([a-zA-Z0-9]+)', handle); + router.get('/measurements/:id', '/measurements/:id([a-zA-Z0-9]+)', handle); }; diff --git a/src/probe/route/get-probes.ts b/src/probe/route/get-probes.ts index 909a3c4d..61181c46 100644 --- a/src/probe/route/get-probes.ts +++ b/src/probe/route/get-probes.ts @@ -37,5 +37,5 @@ const handle = async (ctx: ParameterizedContext { - router.get('/probes', handle); + router.get('/probes', '/probes', handle); }; diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 00000000..d518a0f7 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,7 @@ +import type Koa from 'koa'; +import type Router from '@koa/router'; +import type { DocsLinkContext } from './lib/http/middleware/docs-link'; + +export type CustomState = Koa.DefaultState; +export type CustomContext = Koa.DefaultContext & DocsLinkContext; +export type ExtendedMiddleware = Router.Middleware; diff --git a/test/tests/integration/measurement/create-measurement.test.ts b/test/tests/integration/measurement/create-measurement.test.ts index e5440068..58667e2d 100644 --- a/test/tests/integration/measurement/create-measurement.test.ts +++ b/test/tests/integration/measurement/create-measurement.test.ts @@ -42,6 +42,9 @@ describe('Create measurement', () => { message: 'No suitable probes found.', type: 'no_probes_found', }, + links: { + documentation: 'https://www.jsdelivr.com/docs/api.globalping.io#post-/v1/measurements', + }, }); expect(response).to.matchApiSchema(); @@ -80,6 +83,9 @@ describe('Create measurement', () => { message: 'No suitable probes found.', type: 'no_probes_found', }, + links: { + documentation: 'https://www.jsdelivr.com/docs/api.globalping.io#post-/v1/measurements', + }, }); expect(response).to.matchApiSchema(); @@ -106,6 +112,9 @@ describe('Create measurement', () => { message: 'No suitable probes found.', type: 'no_probes_found', }, + links: { + documentation: 'https://www.jsdelivr.com/docs/api.globalping.io#post-/v1/measurements', + }, }); expect(response).to.matchApiSchema(); @@ -133,6 +142,9 @@ describe('Create measurement', () => { message: 'No suitable probes found.', type: 'no_probes_found', }, + links: { + documentation: 'https://www.jsdelivr.com/docs/api.globalping.io#post-/v1/measurements', + }, }); expect(response).to.matchApiSchema(); @@ -285,6 +297,9 @@ describe('Create measurement', () => { message: 'No suitable probes found.', type: 'no_probes_found', }, + links: { + documentation: 'https://www.jsdelivr.com/docs/api.globalping.io#post-/v1/measurements', + }, }); expect(response).to.matchApiSchema(); diff --git a/test/tests/unit/middleware/error-handler.test.ts b/test/tests/unit/middleware/error-handler.test.ts index eb0a5e0f..789f10ca 100644 --- a/test/tests/unit/middleware/error-handler.test.ts +++ b/test/tests/unit/middleware/error-handler.test.ts @@ -3,33 +3,37 @@ import { expect } from 'chai'; import { errorHandlerMw } from '../../../../src/lib/http/middleware/error-handler.js'; describe('Error handler middleware', () => { + const documentation = 'link://'; + const getDocsLink = () => documentation; + it('should handle http errors', async () => { - const ctx: any = {}; + const ctx: any = { getDocsLink }; + await errorHandlerMw(ctx, () => { throw createHttpError(400, 'bad request'); }); expect(ctx.status).to.equal(400); - expect(ctx.body).to.deep.equal({ error: { message: 'bad request', type: 'api_error' } }); + expect(ctx.body).to.deep.equal({ error: { message: 'bad request', type: 'api_error' }, links: { documentation } }); }); it('should handle http errors with expose=false', async () => { - const ctx: any = {}; + const ctx: any = { getDocsLink }; await errorHandlerMw(ctx, () => { throw createHttpError(400, 'custom error message', { expose: false }); }); expect(ctx.status).to.equal(400); - expect(ctx.body).to.deep.equal({ error: { message: 'Bad Request.', type: 'api_error' } }); + expect(ctx.body).to.deep.equal({ error: { message: 'Bad Request.', type: 'api_error' }, links: { documentation } }); }); it('should handle custom errors', async () => { - const ctx: any = {}; + const ctx: any = { getDocsLink }; await errorHandlerMw(ctx, () => { throw new Error('custom error message'); }); expect(ctx.status).to.equal(500); - expect(ctx.body).to.deep.equal({ error: { message: 'Internal Server Error.', type: 'api_error' } }); + expect(ctx.body).to.deep.equal({ error: { message: 'Internal Server Error.', type: 'api_error' }, links: { documentation } }); }); }); diff --git a/test/tests/unit/middleware/validate.test.ts b/test/tests/unit/middleware/validate.test.ts index 432370cc..6fee8b27 100644 --- a/test/tests/unit/middleware/validate.test.ts +++ b/test/tests/unit/middleware/validate.test.ts @@ -4,6 +4,9 @@ import * as sinon from 'sinon'; import { validate } from '../../../../src/lib/http/middleware/validate.js'; describe('Validate middleware', () => { + const documentation = 'link://'; + const getDocsLink = () => documentation; + const schema = Joi.object({ hello: Joi.string().valid('world!').required(), }); @@ -14,7 +17,7 @@ describe('Validate middleware', () => { }); it('should call next', async () => { - const ctx: any = { request: { body: { hello: 'world!' } } }; + const ctx: any = { request: { body: { hello: 'world!' } }, getDocsLink }; const next = sinon.stub(); await validate(schema)(ctx, next); @@ -24,7 +27,7 @@ describe('Validate middleware', () => { }); it('should return validation error', async () => { - const ctx: any = { request: { body: { hello: 'no one' } } }; + const ctx: any = { request: { body: { hello: 'no one' } }, getDocsLink }; await validate(schema)(ctx, nextMock); @@ -38,11 +41,14 @@ describe('Validate middleware', () => { type: 'validation_error', params: { hello: '"hello" must be [world!]' }, }, + links: { + documentation, + }, }); }); it('should normalise incorrect input case', async () => { - const ctx: any = { request: { body: { input: 'text' } } }; + const ctx: any = { request: { body: { input: 'text' } }, getDocsLink }; const schema = Joi.object({ input: Joi.string().valid('TEXT').insensitive().required(),