{
  "openapi": "3.1.0",
  "info": {
    "title": "UptimeProject Public API",
    "version": "v1",
    "description": "Read-only API for cloud provider uptime rankings and historical\navailability data. Backed by an independent fleet of probes\nmeasuring real cloud services on a 1-minute cadence; aggregated\ninto per-minute and per-day time-series rollups and served as\ncacheable JSON.\n\nAll `/v1/*` GET responses are stamped with\n`Cache-Control: public, max-age=300, s-maxage=300` (5 minutes)\nby default. The OpenAPI spec endpoint uses a longer 1-hour TTL\nbecause the spec only changes on releases.\n\nErrors use a single envelope: `{\"error\": \"<human-readable\nstring>\"}`. The error string is for humans; clients should branch\non the HTTP status code.\n",
    "contact": {
      "url": "https://uptimeproject.org"
    },
    "license": {
      "name": "CC BY 4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    }
  },
  "servers": [
    {
      "url": "https://api.uptimeproject.org",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "services",
      "description": "Per-service metadata, current status, and headline availability."
    },
    {
      "name": "uptime",
      "description": "Time-series availability data for charts and timelines."
    },
    {
      "name": "leaderboard",
      "description": "Ranked best-of lists by category and time window."
    },
    {
      "name": "meta",
      "description": "Machine-readable spec and the docs redirect."
    }
  ],
  "paths": {
    "/v1/services": {
      "get": {
        "tags": [
          "services"
        ],
        "operationId": "listServices",
        "summary": "List active services",
        "description": "Returns every service registered as `active = true` in the\nregistry. Each row carries the service's identity, headline\navailability over the last 30 and 90 days, and current\nconsensus status. Intended for building a directory of\nservices, or for taking a full snapshot of the catalog in\none request.\n\nAvailability values come from the per-day rollup (excluding\n`unknown` minutes from the denominator); a `null` value means\n\"no measurements in the window yet\".\n",
        "responses": {
          "200": {
            "description": "A non-empty array of services. Order is unspecified.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ServiceSummary"
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/services/{id}": {
      "get": {
        "tags": [
          "services"
        ],
        "operationId": "getService",
        "summary": "Fetch a single service by id",
        "description": "Returns the same fields as `/v1/services` for one service,\nplus the active checks that produce its measurements and each\nprobe's most-recent view of the service. Deactivated services\nand unknown ids both return 404: the API does not surface\n\"this service used to exist\" as a distinct state.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/ServiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Service exists and is active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceDetail"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/services/{id}/uptime": {
      "get": {
        "tags": [
          "uptime"
        ],
        "operationId": "getServiceUptime",
        "summary": "Time-series availability for one service",
        "description": "Returns availability data over the requested period as an\nordered series of buckets plus a rolled-up summary. Bucket\nsize is chosen by the period:\n\n| period | bucket size | points |\n|--------|-------------|--------|\n| `24h`  | 5 minutes   | 289    |\n| `7d`   | 1 hour      | 169    |\n| `30d`  | 1 day       | 30     |\n| `90d`  | 1 day       | 90     |\n| `1y`   | 1 day       | 365    |\n\nThe `24h` and `7d` windows are inclusive of both ends, which\nis where the extra point over 288 and 168 comes from. Counts\nassume a full window of history; a service registered more\nrecently returns fewer points.\n\nThe summary's `availability_pct` is computed across the whole\nperiod, not as an average of per-bucket percentages, so partial\nbuckets at the leading edge don't get equal weight to full ones.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/ServiceId"
          },
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "Time window to return. Defaults to `30d`.\n",
            "schema": {
              "$ref": "#/components/schemas/UptimePeriod"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Service exists and the requested period is valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UptimeSeries"
                }
              }
            }
          },
          "400": {
            "description": "The `period` query parameter is not one of the supported values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                },
                "examples": {
                  "invalid_period": {
                    "value": {
                      "error": "invalid period"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/leaderboard": {
      "get": {
        "tags": [
          "leaderboard"
        ],
        "operationId": "getLeaderboard",
        "summary": "Ranked best-of list of active services",
        "description": "Returns active services ranked by average availability over\nthe requested window. Ranks are 1-based and dense, with no\ngaps, ordered by availability descending and broken by name\nascending.\n\nA service with no measurements in the window collapses to\n`availability_pct: 0` with `current_status: unknown` and\nranks at the bottom. It stays in the list rather than\ndisappearing, so a client can tell \"no data yet\" apart from\n\"ranked last\".\n\nThe `category` filter accepts any of the registry-defined\ncategory strings. An unknown value returns 400 rather than an\nempty list, which would mask a typo.\n",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "Window over which availability is averaged. Defaults to\n`30d`. Narrower than the set `/v1/services/{id}/uptime`\naccepts; see `LeaderboardPeriod` for which values are\nexcluded and why.\n",
            "schema": {
              "$ref": "#/components/schemas/LeaderboardPeriod"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Optional category filter. When omitted, all active\nservices rank together; when present, only services in\nthe matching category are ranked.\n",
            "schema": {
              "$ref": "#/components/schemas/Category"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked entries (possibly empty after a category filter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaderboardResponse"
                }
              }
            }
          },
          "400": {
            "description": "`period` is not in the leaderboard whitelist, or\n`category` is non-empty and not a known category.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                },
                "examples": {
                  "invalid_period": {
                    "value": {
                      "error": "invalid period"
                    }
                  },
                  "invalid_category": {
                    "value": {
                      "error": "invalid category"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/openapi.json": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "getOpenAPISpec",
        "summary": "Machine-readable API specification",
        "description": "Returns this OpenAPI 3.1 document as JSON, for clients that\ngenerate code from OpenAPI (e.g. `openapi-typescript`).\n\nServed with a 1-hour TTL, longer than the 5 minutes the data\nendpoints use, because the spec only changes on releases.\n",
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "description": "An OpenAPI 3.1 document. See the OpenAPI specification.",
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/v1/docs": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "getDocsRedirect",
        "summary": "Redirect to the human-readable API reference",
        "description": "Returns a 302 redirect to\n[uptimeproject.org/docs/api](https://uptimeproject.org/docs/api/),\nthe human-readable rendering of this document.\n\nExposed on the API host so a developer who lands on the raw\nbase URL, or a CLI that follows redirects, reaches the\nreadable view in one hop. That page is generated at build\ntime from a copy of this document, so it stays readable while\nthe API is unreachable.\n",
        "responses": {
          "302": {
            "description": "Redirect to the marketing-site docs page.",
            "headers": {
              "Location": {
                "description": "Absolute URL of the reference page.",
                "schema": {
                  "type": "string",
                  "format": "uri",
                  "examples": [
                    "https://uptimeproject.org/docs/api/"
                  ]
                }
              },
              "Cache-Control": {
                "description": "Short TTL (60s) so a future migration of the docs\npage doesn't get pinned in browser caches.\n",
                "schema": {
                  "type": "string",
                  "examples": [
                    "public, max-age=60, s-maxage=60"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ServiceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Slug-style service identifier from the registry, e.g.\n`aws-s3-us-east-1`, `cloudflare-cdn`. Lower-case and\ndash-separated, and stable across releases. Regional\nservices carry their region in the id, so the identifier for\na service is not the provider's product name.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[a-z0-9][a-z0-9-]*$",
          "examples": [
            "aws-s3-us-east-1",
            "cloudflare-cdn"
          ]
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "The service id is not registered, or the service is\ndeactivated. The two cases collapse to the same response;\nthe API does not expose deactivated-vs-never-existed.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError"
            },
            "examples": {
              "not_found": {
                "value": {
                  "error": "not found"
                }
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server-side error. Body is the standard error\nenvelope; the `error` string is intentionally generic and\nclients should not try to parse it for diagnostics.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError"
            },
            "examples": {
              "generic": {
                "value": {
                  "error": "internal server error"
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Status": {
        "type": "string",
        "description": "Probe-consensus status for a service or bucket.\n\n- `up`: at least a majority of probes succeeded\n- `degraded`: some probes failing, but not the majority\n- `down`: every probe failed\n- `unknown`: fewer than 3 probes reported, which is too few\n  to call a consensus\n",
        "enum": [
          "up",
          "down",
          "degraded",
          "unknown"
        ]
      },
      "UptimePeriod": {
        "type": "string",
        "description": "Period accepted by `/v1/services/{id}/uptime`. Bucket size\nis fixed per period (see endpoint description).\n",
        "enum": [
          "24h",
          "7d",
          "30d",
          "90d",
          "1y"
        ],
        "default": "30d"
      },
      "LeaderboardPeriod": {
        "type": "string",
        "description": "Period accepted by `/v1/leaderboard`. Subset of `UptimePeriod`:\n`24h` is excluded as too noisy for ranking (one bad hour would\nflip a service to last place); `1y` is excluded as too lagging\n(rewards last-year stability over current performance).\n",
        "enum": [
          "7d",
          "30d",
          "90d"
        ],
        "default": "30d"
      },
      "Category": {
        "type": "string",
        "description": "Service category. Mirrors the registry's category names\nverbatim, so the value is `object-storage` (hyphenated),\nnot `storage`.\n",
        "examples": [
          "object-storage"
        ],
        "enum": [
          "iaas",
          "cdn",
          "dns",
          "object-storage",
          "dev-infra",
          "email",
          "payments",
          "ai"
        ]
      },
      "APIError": {
        "type": "object",
        "description": "Canonical error envelope used by every non-2xx response.\nThe `error` string is human-readable and may change between\ndeploys, so clients should branch on the HTTP status.\n",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message.",
            "examples": [
              "not found",
              "invalid period",
              "invalid category",
              "internal server error"
            ]
          }
        }
      },
      "ServiceSummary": {
        "type": "object",
        "description": "One row of `/v1/services`, and the base that `ServiceDetail`\nextends. Carries identity, headline availability, current\nconsensus status, and the number of active checks, so a\nclient can summarise the whole catalog without fetching each\nservice.\n",
        "required": [
          "id",
          "name",
          "category",
          "show_on_leaderboard",
          "homepage_url",
          "current_status",
          "checks_count",
          "availability_24h",
          "availability_7d",
          "availability_30d",
          "availability_90d"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Slug-style identifier from the registry.",
            "examples": [
              "aws-s3-us-east-1"
            ]
          },
          "name": {
            "type": "string",
            "description": "Human-readable service name.",
            "examples": [
              "AWS S3"
            ]
          },
          "category": {
            "$ref": "#/components/schemas/Category"
          },
          "region": {
            "type": "string",
            "description": "Provider region for regional services. Omitted for\nglobal services (CDNs, DNS resolvers, payment APIs).\n",
            "examples": [
              "us-east-1"
            ]
          },
          "provider": {
            "type": "string",
            "description": "Brand or parent company behind the service. Editorial\nrather than derived from `id` or `name`: every `aws-*`\nservice reports \"Amazon Web Services\" whatever the\nproduct is called, and `linode` reports \"Akamai / Linode\"\nfor the parent company. Omitted when the registry row\ncarries no provider.\n",
            "examples": [
              "Amazon Web Services",
              "Cloudflare",
              "Akamai / Linode"
            ]
          },
          "show_on_leaderboard": {
            "type": "boolean",
            "description": "Whether the service appears on public ranking surfaces.\n`true`, the default, lists it on the leaderboard and the\nservices index. `false` keeps it probed and queryable\nthrough `/v1/services/{id}` and\n`/v1/services/{id}/uptime` but off those rankings, which\nis how a newly onboarded service is held back until it\nhas enough history to rank fairly.\n\nAlways present, never omitted: an absent field would read\nas \"the API does not know\" rather than \"hidden\".\n",
            "examples": [
              true,
              false
            ]
          },
          "homepage_url": {
            "type": "string",
            "format": "uri",
            "description": "Public landing page for the service. Always present.\n",
            "examples": [
              "https://aws.amazon.com/s3/"
            ]
          },
          "status_page_url": {
            "type": "string",
            "format": "uri",
            "description": "The provider's own status page, where it publishes one.\nOmitted rather than empty when it does not, so a client\nnever has to special-case `\"\"`.\n\nCarried on the list endpoint as well as the detail one,\nso rendering a directory of services needs a single\nrequest.\n",
            "examples": [
              "https://status.aws.amazon.com/rss/s3-us-east-1.rss"
            ]
          },
          "current_status": {
            "$ref": "#/components/schemas/Status"
          },
          "checks_count": {
            "type": "integer",
            "description": "Number of active checks measuring this service. Checks\nregistered but not yet active are excluded, so the count\nmatches what the fleet is really executing. `0` for a\nnewly registered service whose checks have not landed\nyet.\n",
            "minimum": 0,
            "examples": [
              3,
              1
            ]
          },
          "availability_24h": {
            "type": [
              "number",
              "null"
            ],
            "description": "Availability percent over the rolling trailing 24 hours,\ncomputed from the per-minute consensus aggregate, with\n`unknown` minutes excluded from the denominator.\n\nMeasured directly rather than derived from the per-day\nrollup, whose buckets are anchored to UTC midnight:\naveraging a partial today against yesterday would bias\nagainst a service that was unhealthy hours ago. `null`\nwhen the window has no measurements yet.\n",
            "minimum": 0,
            "maximum": 100,
            "examples": [
              99.99,
              null
            ]
          },
          "availability_7d": {
            "type": [
              "number",
              "null"
            ],
            "description": "Availability percent over the trailing 7 days, derived\nfrom the per-day rollup (same source as `availability_30d`\n/ `availability_90d`). `null` when the window has no\nmeasurements yet.\n",
            "minimum": 0,
            "maximum": 100,
            "examples": [
              99.98,
              null
            ]
          },
          "availability_30d": {
            "type": [
              "number",
              "null"
            ],
            "description": "Availability percent over the trailing 30 days, derived\nfrom the per-day rollup (excludes `unknown` minutes from\nthe denominator). `null` when the window has no\nmeasurements yet.\n",
            "minimum": 0,
            "maximum": 100,
            "examples": [
              99.97,
              null
            ]
          },
          "availability_90d": {
            "type": [
              "number",
              "null"
            ],
            "description": "Same as `availability_30d`, over 90 days.",
            "minimum": 0,
            "maximum": 100,
            "examples": [
              99.91,
              null
            ]
          }
        }
      },
      "ServiceDetail": {
        "type": "object",
        "description": "Body of `GET /v1/services/{id}`. Every field of\n`ServiceSummary`, plus the active checks that produce the\nmeasurements and each probe's most-recent view of the\nservice.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/ServiceSummary"
          },
          {
            "type": "object",
            "required": [
              "checks",
              "probes"
            ],
            "properties": {
              "checks": {
                "type": "array",
                "description": "Active checks for this service. Always an array,\nnever `null`, and empty for a newly registered\nservice whose checks have not landed yet.\n",
                "items": {
                  "$ref": "#/components/schemas/CheckSummary"
                }
              },
              "probes": {
                "type": "array",
                "description": "Each vantage point's current view of the service,\ntaken from the most-recent minute that probe\nreported on, within a 5-minute lookback. A probe\nthat has not reported inside that window is absent\nfrom the array rather than listed as unknown.\nAlways an array, never `null`, and empty for a\nservice with no recent measurements.\n",
                "items": {
                  "$ref": "#/components/schemas/ProbeStatus"
                }
              }
            }
          }
        ]
      },
      "CheckSummary": {
        "type": "object",
        "description": "One active check on a service. Published so that what is\nmeasured, and how, can be checked rather than taken on\ntrust.\n",
        "required": [
          "id",
          "check_type",
          "target",
          "interval_sec"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable check identifier, formed from the service id, the\ncheck type and the target it covers.\n",
            "examples": [
              "aws-s3-us-east-1-http-main"
            ]
          },
          "check_type": {
            "type": "string",
            "description": "Probe type. Currently one of `http`, `tls`, `storage`,\n`dns`. Typed as a string rather than an enum so adding a\nprobe type stays a non-breaking change.\n",
            "examples": [
              "http"
            ]
          },
          "target": {
            "type": "string",
            "description": "What the probe hits: a URL, an IP and port, a hostname.\nThe format depends on `check_type`.\n\nWhere the real target embeds a private identifier, such\nas a storage bucket name, the API substitutes a\n`redacted` placeholder of the same shape (for example\n`https://redacted.s3.eu-west-1.amazonaws.com/probe.bin`).\nThe check is still described, down to the operation and\nthe region, without publishing the identifier. Only the\npublic wire shape is substituted; the probe hits the\nreal target.\n",
            "examples": [
              "https://s3.us-east-1.amazonaws.com/",
              "https://redacted.s3.eu-west-1.amazonaws.com/probe.bin"
            ]
          },
          "interval_sec": {
            "type": "integer",
            "description": "Probe cadence in seconds.",
            "minimum": 1,
            "examples": [
              60
            ]
          }
        }
      },
      "ProbeStatus": {
        "type": "object",
        "description": "One vantage point's most-recent view of a service, returned\ninside `ServiceDetail.probes`.\n\nStatus uses the same vocabulary as the service-wide\nconsensus (`up`, `down`, `degraded`) but is computed per\nprobe, across the service's checks within the most-recent\nminute that probe reported on: `up` if every check\nsucceeded, `down` if none did, `degraded` in between. There\nis no `unknown`, because a probe that has not reported\ninside the 5-minute lookback is absent from the array.\n",
        "required": [
          "probe_id",
          "status",
          "latency_ms"
        ],
        "properties": {
          "probe_id": {
            "type": "string",
            "description": "Stable probe identifier from the fleet registry, e.g.\n`hzr-fsn1`. Use it as the key when joining `probes`\nagainst probe metadata such as location and provider.\n",
            "examples": [
              "hzr-fsn1",
              "ovh-rbx",
              "do-nyc3"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "up",
              "down",
              "degraded"
            ],
            "description": "Per-probe consensus across the service's checks within\nthe most-recent reported minute. `unknown` is not valid\nhere: a probe with no recent report is absent from the\narray entirely.\n",
            "examples": [
              "up"
            ]
          },
          "latency_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "p50 latency this probe observed in the most-recent\nminute, taken as the maximum across the service's\nchecks, so a service whose HTTPS check is fast and TLS\ncheck is slow reports the slower number.\n\n`null` when every measurement in that minute failed,\nwhich is a different case from the probe not reporting\nat all: that probe is absent from the array.\n",
            "minimum": 0,
            "examples": [
              42,
              null
            ]
          }
        }
      },
      "UptimePoint": {
        "type": "object",
        "description": "One bucket on the uptime time series. Bucket size depends\non the requested period (see `/v1/services/{id}/uptime`).\n",
        "required": [
          "bucket",
          "availability_pct",
          "status",
          "down_minutes",
          "degraded_minutes",
          "latency_p50_ms"
        ],
        "properties": {
          "bucket": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp at the start of the bucket, UTC, RFC 3339.\n",
            "examples": [
              "2026-05-08T09:00:00Z"
            ]
          },
          "availability_pct": {
            "type": "number",
            "description": "Percent of measured minutes in the bucket that were\nclassified `up`. `0` when the bucket has no measured\nminutes (in which case `status` is `unknown`).\n",
            "minimum": 0,
            "maximum": 100
          },
          "status": {
            "$ref": "#/components/schemas/Status"
          },
          "down_minutes": {
            "type": "integer",
            "description": "Count of minutes in the bucket with consensus `down`.",
            "minimum": 0
          },
          "degraded_minutes": {
            "type": "integer",
            "description": "Count of minutes in the bucket with consensus `degraded`.",
            "minimum": 0
          },
          "latency_p50_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Success-count-weighted p50 (median) response time, in\nmilliseconds, across every check and probe pair inside\nthe bucket. `null` when the bucket contained no\nsuccessful measurement, so a chart should break the\nlatency line there rather than plot a zero.\n",
            "minimum": 0,
            "examples": [
              87,
              null
            ]
          }
        }
      },
      "UptimeSummary": {
        "type": "object",
        "description": "Rolled-up totals for the whole period. `availability_pct`\nis count-weighted across every measured minute (NOT an\naverage of per-bucket percentages) so partial buckets\ndon't get equal weight to full ones.\n",
        "required": [
          "availability_pct",
          "total_down_minutes",
          "total_degraded_minutes",
          "latency_p50_ms"
        ],
        "properties": {
          "availability_pct": {
            "type": "number",
            "description": "`100 * total_up / (total_up + total_down + total_degraded)`\nacross every measured minute in the period. `0` when\nno minute had measured data.\n",
            "minimum": 0,
            "maximum": 100
          },
          "total_down_minutes": {
            "type": "integer",
            "description": "Sum of `down_minutes` across every point.",
            "minimum": 0
          },
          "total_degraded_minutes": {
            "type": "integer",
            "description": "Sum of `degraded_minutes` across every point.",
            "minimum": 0
          },
          "latency_p50_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Success-count-weighted p50 response time, in\nmilliseconds, across every successful measurement in\nthe period. `null` when the period contained zero\nsuccessful measurements. Useful as the headline\n\"Average response time\" for a service detail page.\n",
            "minimum": 0,
            "examples": [
              142,
              null
            ]
          }
        }
      },
      "UptimeSeries": {
        "type": "object",
        "description": "Body of `GET /v1/services/{id}/uptime`.",
        "required": [
          "service_id",
          "period",
          "points",
          "summary"
        ],
        "properties": {
          "service_id": {
            "type": "string",
            "description": "Echoed back from the path parameter.",
            "examples": [
              "aws-s3-us-east-1"
            ]
          },
          "period": {
            "$ref": "#/components/schemas/UptimePeriod"
          },
          "points": {
            "type": "array",
            "description": "Buckets in ascending bucket-time order. Always\nrenders as a JSON array (never `null`); empty `[]`\nis valid for a service with no measurements yet.\n",
            "items": {
              "$ref": "#/components/schemas/UptimePoint"
            }
          },
          "summary": {
            "$ref": "#/components/schemas/UptimeSummary"
          }
        }
      },
      "LeaderboardEntry": {
        "type": "object",
        "description": "One ranked row in `/v1/leaderboard`.",
        "required": [
          "rank",
          "service_id",
          "name",
          "category",
          "availability_pct",
          "current_status"
        ],
        "properties": {
          "rank": {
            "type": "integer",
            "description": "1-based, dense rank within this response. Tie-breaker\nis `name ASC`. Clients should NOT recompute rank;\nthe server's tie-breaker is part of the methodology.\n",
            "minimum": 1
          },
          "service_id": {
            "type": "string",
            "description": "Slug-style identifier; cross-references `/v1/services/{id}`.",
            "examples": [
              "aws-s3-us-east-1"
            ]
          },
          "name": {
            "type": "string",
            "description": "Human-readable service name.",
            "examples": [
              "AWS S3"
            ]
          },
          "category": {
            "$ref": "#/components/schemas/Category"
          },
          "availability_pct": {
            "type": "number",
            "description": "Average availability percent over the requested period.\nServices with no measurements collapse to `0` (paired\nwith `current_status: unknown`).\n",
            "minimum": 0,
            "maximum": 100
          },
          "current_status": {
            "$ref": "#/components/schemas/Status"
          },
          "rank_change": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Rank movement against the previous publication, positive\nfor an improvement and negative for a decline. Always\nabsent in v1, because historical leaderboard state is\nnot persisted yet. The field appears once it is.\n"
          }
        }
      },
      "LeaderboardResponse": {
        "type": "object",
        "description": "Body of `GET /v1/leaderboard`.",
        "required": [
          "period",
          "entries",
          "generated_at"
        ],
        "properties": {
          "period": {
            "$ref": "#/components/schemas/LeaderboardPeriod"
          },
          "category": {
            "$ref": "#/components/schemas/Category",
            "description": "Echoed when a `?category=` filter was applied;\nabsent on the unfiltered call.\n"
          },
          "entries": {
            "type": "array",
            "description": "Ranked rows, ordered by `rank ASC`. Always renders\nas a JSON array (never `null`); empty `[]` is valid\nafter a category filter that matches no services.\n",
            "items": {
              "$ref": "#/components/schemas/LeaderboardEntry"
            }
          },
          "generated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Server wall-clock at response build time, UTC, RFC 3339.\nUseful for cache debugging, and for rendering an \"as of\"\nline alongside a ranking.\n",
            "examples": [
              "2026-05-08T09:00:00Z"
            ]
          }
        }
      }
    }
  }
}
