From 9aa2594df7a9ac523624cc02740dc1804a0f1708 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Fri, 22 May 2026 10:21:10 -0400 Subject: [PATCH] docs(api): add public API documentation site (Astro Starlight) ## Changes - Add narrative API documentation in docs/api-content/ (introduction, quickstart, authentication, conventions, errors, rate limiting) - Scaffold Astro Starlight site at docs/api/ that consumes prose from the sibling api-content folder and auto-generates the endpoint reference from server/openapi.json via starlight-openapi - Wire Checkmate brand color (#13715B) into Starlight via custom CSS - Add vercel.json so the docs site deploys cleanly as an Astro project with root directory docs/api/ ## Benefits - Public API docs ready to publish at checkmate.so/docs - Endpoint reference stays in sync with the OpenAPI spec automatically - Zero third-party SaaS dependency: site is fully self-hosted on Vercel - Contributors edit plain markdown; no Astro knowledge needed for prose --- docs/api-content/README.md | 114 +++++++++++++++++ docs/api-content/api-reference/overview.md | 47 +++++++ docs/api-content/authentication.md | 137 +++++++++++++++++++++ docs/api-content/conventions.md | 122 ++++++++++++++++++ docs/api-content/errors.md | 124 +++++++++++++++++++ docs/api-content/introduction.md | 84 +++++++++++++ docs/api-content/quickstart.md | 117 ++++++++++++++++++ docs/api-content/rate-limiting.md | 83 +++++++++++++ docs/api/.gitignore | 16 +++ docs/api/astro.config.mjs | 74 +++++++++++ docs/api/package.json | 20 +++ docs/api/src/assets/logo.svg | 5 + docs/api/src/content.config.ts | 13 ++ docs/api/src/styles/checkmate.css | 23 ++++ docs/api/tsconfig.json | 5 + docs/api/vercel.json | 7 ++ 16 files changed, 991 insertions(+) create mode 100644 docs/api-content/README.md create mode 100644 docs/api-content/api-reference/overview.md create mode 100644 docs/api-content/authentication.md create mode 100644 docs/api-content/conventions.md create mode 100644 docs/api-content/errors.md create mode 100644 docs/api-content/introduction.md create mode 100644 docs/api-content/quickstart.md create mode 100644 docs/api-content/rate-limiting.md create mode 100644 docs/api/.gitignore create mode 100644 docs/api/astro.config.mjs create mode 100644 docs/api/package.json create mode 100644 docs/api/src/assets/logo.svg create mode 100644 docs/api/src/content.config.ts create mode 100644 docs/api/src/styles/checkmate.css create mode 100644 docs/api/tsconfig.json create mode 100644 docs/api/vercel.json diff --git a/docs/api-content/README.md b/docs/api-content/README.md new file mode 100644 index 000000000..a7989654f --- /dev/null +++ b/docs/api-content/README.md @@ -0,0 +1,114 @@ +# Checkmate API documentation + +Public-facing API documentation that powers **https://checkmate.so/docs**. + +The site is built with [Astro Starlight](https://starlight.astro.build/) — a static-site generator that produces a fast, modern docs site with zero JavaScript by default. Starlight is open source and self-hostable; there is no third-party account required to publish. + +## Repository layout + +``` +docs/ +├── api-content/ ← markdown prose (this folder — edit here) +│ ├── introduction.md +│ ├── quickstart.md +│ ├── authentication.md +│ ├── conventions.md +│ ├── errors.md +│ ├── rate-limiting.md +│ └── api-reference/ +│ └── overview.md +└── api/ ← Astro Starlight project (build tool — leave alone unless changing the site) + ├── package.json + ├── astro.config.mjs + ├── src/content.config.ts + └── src/styles/checkmate.css +``` + +**Contributors editing prose only need to touch `docs/api-content/`**. Astro's content loader picks up the markdown automatically. + +The endpoint reference (every URL + schema) is **auto-generated from [`server/openapi.json`](../../server/openapi.json)** via the `starlight-openapi` plugin. Adding or changing an endpoint means updating that JSON; the public docs pick it up on the next Vercel build. + +## Local preview + +```bash +cd docs/api +npm install +npm run dev +``` + +Visit http://localhost:4321/docs. Hot-reload works for markdown changes in `docs/api-content/`. + +To produce the static site: + +```bash +npm run build # outputs docs/api/dist/ +npm run preview # serve the built output locally +``` + +## Deploying to checkmate.so/docs + +Astro Starlight builds a static site. Deploy on Vercel and reverse-proxy `checkmate.so/docs/*` to it. + +### One-time Vercel setup + +1. Go to https://vercel.com → New Project → import the `bluewave-labs/Checkmate` GitHub repo. +2. **Root Directory**: set to `docs/api`. +3. **Framework Preset**: Vercel auto-detects Astro from `vercel.json`. +4. **Build & Output**: leave defaults (`npm run build` → `dist/`). +5. **Production Branch**: `develop`. +6. Deploy. Vercel returns a URL like `checkmate-docs.vercel.app`. +7. Verify the site renders correctly. + +The deployment auto-rebuilds on every push to `develop` and on every PR (as a preview deploy). + +### Reverse-proxy /docs to Vercel + +In whichever edge proxy fronts `checkmate.so`, route `/docs/*` to the Vercel deployment. + +**Cloudflare Workers:** + +```javascript +addEventListener("fetch", (event) => { + const url = new URL(event.request.url); + if (url.pathname === "/docs" || url.pathname.startsWith("/docs/")) { + const target = new URL(url.pathname, "https://checkmate-docs.vercel.app"); + target.search = url.search; + return event.respondWith(fetch(target.toString(), event.request)); + } +}); +``` + +**nginx:** + +```nginx +location /docs/ { + proxy_pass https://checkmate-docs.vercel.app/docs/; + proxy_set_header Host checkmate-docs.vercel.app; + proxy_ssl_server_name on; +} +``` + +The `base: "/docs"` setting in `astro.config.mjs` ensures every internal link and asset URL is prefixed correctly so the site works at `checkmate.so/docs/...` rather than the root. + +## Updating the docs + +| Task | What to edit | +|---|---| +| Fix a typo, clarify wording | `docs/api-content/*.md` | +| Add a new narrative page | Add the file to `docs/api-content/`, then add a `slug` entry to `astro.config.mjs` → `sidebar` | +| Fix something in the API reference | Edit `server/openapi.json` (or the route spec it's built from) — never edit the generated reference pages | +| Change branding / colors | `docs/api/src/styles/checkmate.css` | +| Change site config (title, logo, social) | `docs/api/astro.config.mjs` | + +Two rules: + +1. **Never hand-edit the API reference.** It's regenerated on every build. Fix the spec. +2. **Test code samples against a real instance before merging.** Wrong code in docs is worse than missing code. + +## Why Astro Starlight (and not Mintlify, Docusaurus, etc.) + +- **No third-party account required.** Self-hostable on any static host. +- **Best looking** of the modern docs frameworks per multiple 2026 reviews — Distr, among others, migrated away from Docusaurus specifically for Starlight's design. +- **Zero JavaScript by default.** Pages are pure HTML — fast load, perfect Lighthouse scores. +- **OpenAPI integration is first-class** via `starlight-openapi`. +- **Markdown is the source of truth.** No proprietary formats. Easy to migrate later if needed. diff --git a/docs/api-content/api-reference/overview.md b/docs/api-content/api-reference/overview.md new file mode 100644 index 000000000..71a9af87e --- /dev/null +++ b/docs/api-content/api-reference/overview.md @@ -0,0 +1,47 @@ +--- +title: API reference overview +description: All endpoints, grouped by resource. Auto-generated from the OpenAPI spec. +--- + +# API reference + +The full machine-readable specification is at [`server/openapi.json`](https://github.com/bluewave-labs/Checkmate/blob/develop/server/openapi.json). Mintlify renders an interactive reference from it — each endpoint below is fully documented with request/response schemas, examples, and a built-in "Try it" form. + +## Endpoint groups + +| Group | Description | +|---|---| +| **auth** | Register, log in, password recovery, manage users | +| **invite** | Send and redeem team invites | +| **monitors** | Create, update, pause, and delete monitors | +| **checks** | Read individual check results and team-wide summaries | +| **incidents** | List, filter, and resolve downtime incidents | +| **maintenance-window** | Schedule maintenance windows to silence checks | +| **notifications** | Configure email, Slack, Discord, and webhook channels | +| **status-page** | Manage public status pages | +| **geo-checks** | Multi-region check results | +| **logs** | Audit and operational logs | +| **settings** | Global Checkmate settings | +| **queue** | Background-job queue health (operational) | +| **diagnostic** | System health and version info | + +## Authentication + +Every endpoint except the unauthenticated auth/recovery routes requires: + +``` +Authorization: Bearer +``` + +See [Authentication](/api/authentication) for how to obtain a token. + +## Conventions recap + +- Base URL: `https://your-checkmate-host/api/v1` +- Response envelope: `{ success, msg, data }` +- IDs: 24-character MongoDB ObjectIds +- Timestamps: ISO 8601 UTC +- Durations: integers in milliseconds +- Most resources are team-scoped — pass `teamId` where the path includes `/team/...` + +See [Conventions](/api/conventions) for full details. diff --git a/docs/api-content/authentication.md b/docs/api-content/authentication.md new file mode 100644 index 000000000..48a480e51 --- /dev/null +++ b/docs/api-content/authentication.md @@ -0,0 +1,137 @@ +--- +title: Authentication +description: Obtain a JWT from /auth/login and send it as a Bearer token on every subsequent request. +--- + +# Authentication + +Checkmate uses **JWT bearer tokens**. There are no API keys, no OAuth, and no client secrets. Every API user is a regular Checkmate user — the credentials you log in to the web UI with are the credentials you use to obtain a token. + +## The flow + +1. **Get a token** by calling `POST /auth/login` with your email and password. +2. **Send the token** as `Authorization: Bearer ` on every subsequent request. +3. **Refresh** by calling `POST /auth/login` again before the token expires. + +## Obtaining a token + +```bash +curl -X POST https://your-checkmate-host/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "email": "you@example.com", + "password": "YourPass1!" + }' +``` + +A successful response returns the token plus the authenticated user record: + +```json +{ + "success": true, + "msg": "OK", + "data": { + "user": { + "_id": "65f1c2a4d8b9e0123456789a", + "firstName": "Ada", + "lastName": "Lovelace", + "email": "you@example.com", + "role": ["admin"], + "teamId": "65f1c2a4d8b9e01234567890", + "createdAt": "2026-04-01T10:00:00.000Z", + "updatedAt": "2026-04-15T14:30:00.000Z" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } +} +``` + +Store both `token` (for the `Authorization` header) and `user.teamId` (most resource endpoints are scoped by team). + +## Using a token + +Every authenticated request must include: + +``` +Authorization: Bearer +``` + +```bash +curl https://your-checkmate-host/api/v1/monitors/team/ \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + +```javascript +const res = await fetch("https://your-checkmate-host/api/v1/monitors/team/", { + headers: { Authorization: `Bearer ${token}` }, +}); +``` + +## Token lifetime + +Tokens expire after the duration configured by the operator via the `TOKEN_TTL` environment variable. The default is **99 days**. + +There is no separate refresh-token endpoint. When a token is close to expiry — or after a 401 response — call `POST /auth/login` again to obtain a new one. + +## Roles + +The `user.role` claim is an array. Available roles: + +| Role | Capability | +|---|---| +| `superadmin` | Full access to all teams and global settings. Can promote other users. | +| `admin` | Full access within their team. | +| `user` | Read access within their team. | +| `demo` | Limited demo-mode access. | + +A user can hold multiple roles simultaneously. + +## Registering the first user + +A fresh Checkmate instance has no users. The first call to `POST /auth/register` creates the first **superadmin** and provisions a team for them: + +```bash +curl -X POST https://your-checkmate-host/api/v1/auth/register \ + -H "Content-Type: application/json" \ + -d '{ + "firstName": "Ada", + "lastName": "Lovelace", + "email": "ada@example.com", + "password": "Hunter2!", + "role": ["superadmin"], + "teamId": "" + }' +``` + +After the first user exists, additional users are usually created via the invite flow rather than `POST /auth/register`: + +``` +POST /invite/send # superadmin/admin sends an invite +POST /auth/register # invitee redeems the invite token +``` + +You can check whether a superadmin already exists with: + +```bash +curl https://your-checkmate-host/api/v1/auth/users/superadmin +``` + +```json +{ "success": true, "data": true } +``` + +## Password recovery + +The password-recovery flow is three calls, all unauthenticated: + +``` +POST /auth/recovery/request # user requests a recovery email +POST /auth/recovery/validate # client validates the recovery token +POST /auth/recovery/reset # user sets a new password +``` + +## Logging out + +JWTs are stateless — there is no server-side logout. Drop the token from your client storage. If you need to invalidate a token before its expiry (e.g. a compromised device), the operator must rotate the `JWT_SECRET` environment variable, which invalidates every outstanding token. + +Next: [Conventions](/api/conventions). diff --git a/docs/api-content/conventions.md b/docs/api-content/conventions.md new file mode 100644 index 000000000..492b4e8d1 --- /dev/null +++ b/docs/api-content/conventions.md @@ -0,0 +1,122 @@ +--- +title: Conventions +description: Response envelope, request format, identifiers, pagination, and team scoping. +--- + +# Conventions + +A small number of conventions apply to every endpoint. Once you've internalized them, the per-resource reference is mostly self-explanatory. + +## Request format + +- **Content-Type**: `application/json` for all POST/PUT/PATCH bodies, except multipart uploads (logos, profile images), which use `multipart/form-data`. +- **Charset**: UTF-8. +- **Method semantics**: + - `GET` — read, never mutates state. + - `POST` — create, or action that doesn't fit GET/PATCH/DELETE. + - `PATCH` — partial update. + - `PUT` — full replace. + - `DELETE` — delete. + +## Response envelope + +Every JSON response uses the same shape: + +```json +{ + "success": true, + "msg": "OK", + "data": { /* endpoint-specific payload */ } +} +``` + +| Field | Type | Meaning | +|---|---|---| +| `success` | boolean | `true` on 2xx, `false` on 4xx/5xx | +| `msg` | string | Human-readable summary — safe to surface in UIs | +| `data` | object \| array \| null | Endpoint-specific payload. Absent or `null` for `204 No Content`. | + +Error responses use the same envelope with `success: false` and `data` typically omitted. See [Errors](/api/errors). + +## Identifiers + +- All resource IDs are **MongoDB ObjectIds** (24-character hex strings): + ``` + 65f1c2a4d8b9e0123456789a + ``` +- Treat IDs as opaque strings. Do not attempt to parse the timestamp or counter components. + +## Team scoping + +Most resources belong to a **team**. After login, the user record contains a `teamId`; pass it as a path parameter wherever the endpoint name includes `/team/...`: + +``` +GET /monitors/team/65f1c2a4d8b9e01234567890 +GET /incidents/team +GET /checks/team +``` + +Users only see resources belonging to their own team, except for `superadmin`, who can read across teams. + +## Date and time + +- All timestamps in JSON are **ISO 8601** strings in UTC: + ``` + "createdAt": "2026-04-01T10:00:00.000Z" + ``` +- Durations are typically integers in **milliseconds** (intervals, response times, time windows). + +## Pagination + +Endpoints that return collections accept these optional query parameters: + +| Parameter | Type | Default | Notes | +|---|---|---|---| +| `limit` | integer | endpoint-dependent | Maximum rows to return. | +| `page` | integer | `0` | Zero-indexed page number. | +| `sortBy` | string | endpoint-dependent | Field to sort by. | +| `sortOrder` | `asc` \| `desc` | `desc` | Sort direction. | +| `filter` | string | — | Endpoint-specific filter (e.g. status). | + +When a collection supports pagination, the response `data` includes the rows plus the total count: + +```json +{ + "success": true, + "msg": "OK", + "data": { + "monitors": [ /* … */ ], + "filteredMonitors": [ /* … */ ], + "summary": { /* counters */ } + } +} +``` + +Exact shapes vary per endpoint. See the [API reference](/api/reference) for each endpoint's response schema. + +## File uploads + +Two endpoints accept files: + +- Status page logo: `POST /status-page` (multipart, field `logo`) +- User profile picture: `POST /auth/register`, `PATCH /auth/user/:id` (multipart, field `profileImage`) + +Use `multipart/form-data` and include the JSON fields as separate form parts. + +## CORS + +The server's CORS policy is configured via the `CLIENT_HOST` environment variable. Only requests whose `Origin` matches `CLIENT_HOST` are accepted, and credentials (cookies) are allowed. + +If you're calling the API from a browser on a different origin, the operator must add that origin to `CLIENT_HOST`. Server-to-server calls (curl, your backend) are not subject to CORS. + +## Versioning + +The base path includes the API version: + +``` +/api/v1/... +``` + +Within `v1`, additive changes (new endpoints, new optional fields) ship without notice. Breaking changes (removed endpoints, changed field types) only ship under a new major version. + +Next: [Errors](/api/errors). diff --git a/docs/api-content/errors.md b/docs/api-content/errors.md new file mode 100644 index 000000000..32c9befaa --- /dev/null +++ b/docs/api-content/errors.md @@ -0,0 +1,124 @@ +--- +title: Errors +description: HTTP status codes Checkmate returns, what they mean, and how to handle them. +--- + +# Errors + +Failure responses share the same envelope as successful ones, with `success: false` and `data` omitted (or `null`): + +```json +{ + "success": false, + "msg": "Validation failed: email must be a valid email" +} +``` + +Always treat the **HTTP status code** as the source of truth for handling logic, and surface `msg` to humans. + +## Status codes + +| Status | Meaning | When you'll see it | +|---|---|---| +| `200 OK` | Request succeeded with a body | Most GETs, successful mutations that return the new resource | +| `204 No Content` | Request succeeded with no body | DELETEs, successful actions like `/status-page/:url/unlock` | +| `400 Bad Request` | Malformed request | Missing required path param, malformed JSON, invalid file upload | +| `401 Unauthorized` | Missing or invalid JWT | Token expired, token missing, token signed with a different secret | +| `403 Forbidden` | Authenticated, but not allowed | Accessing a resource that belongs to a different team, or insufficient role | +| `404 Not Found` | Resource doesn't exist | Wrong ID, deleted resource, mistyped path | +| `409 Conflict` | Operation would violate a constraint | Duplicate email on register, slug already taken on status page | +| `413 Payload Too Large` | File upload exceeded limit | Logo or profile image too big | +| `422 Unprocessable Entity` | Request was well-formed but failed validation | Zod / Joi schema rejected the body | +| `429 Too Many Requests` | Rate limit exceeded | See [Rate limiting](/api/rate-limiting) | +| `500 Internal Server Error` | Unexpected server error | A bug — report it with the request ID | + +## Common shapes + +### 401 — missing or invalid token + +```json +{ + "success": false, + "msg": "No auth token provided" +} +``` + +Re-authenticate by calling `POST /auth/login`. + +### 422 — validation error + +```json +{ + "success": false, + "msg": "\"email\" must be a valid email" +} +``` + +The `msg` describes the first failing field. Re-send the request with the fix. + +### 403 — forbidden + +```json +{ + "success": false, + "msg": "Forbidden" +} +``` + +You're authenticated but the resource isn't visible to you. Common cause: passing a `teamId` that isn't your team's. + +### 429 — rate limited + +```json +{ + "success": false, + "msg": "Too many requests" +} +``` + +Inspect `RateLimit-*` response headers and back off. See [Rate limiting](/api/rate-limiting). + +## Error handling pattern + +Client code should branch on status code, not on `msg`: + +```javascript +const res = await fetch(url, opts); +const body = await res.json().catch(() => ({})); + +switch (res.status) { + case 200: + case 204: + return body.data; + + case 401: + // Token expired or missing — re-authenticate, then retry once. + await reauthenticate(); + return retry(); + + case 422: + // Surface the validation error to the user. + throw new ValidationError(body.msg); + + case 429: + // Back off using Retry-After if present. + const retryAfter = Number(res.headers.get("Retry-After") ?? 60); + await sleep(retryAfter * 1000); + return retry(); + + default: + throw new Error(`Checkmate API ${res.status}: ${body.msg ?? res.statusText}`); +} +``` + +## Reporting bugs + +If you see a `500`, capture: + +- The exact request (method, path, body — **redact secrets**) +- The full response body and headers +- A timestamp in UTC + +Open an issue at https://github.com/bluewave-labs/Checkmate/issues with that information. + +Next: [Rate limiting](/api/rate-limiting). diff --git a/docs/api-content/introduction.md b/docs/api-content/introduction.md new file mode 100644 index 000000000..2e3d99e05 --- /dev/null +++ b/docs/api-content/introduction.md @@ -0,0 +1,84 @@ +--- +title: Introduction +description: Use the Checkmate REST API to manage monitors, incidents, status pages, notifications, and users programmatically. +--- + +# Checkmate API + +Checkmate exposes a REST API that mirrors everything the web UI can do. Use it to provision monitors from infrastructure-as-code, build internal dashboards, automate incident workflows, or integrate Checkmate into your existing alerting stack. + +The web UI you log in to is itself just a client of this same API. + +## Base URL + +``` +https:///api/v1 +``` + +Replace `` with the domain where you deployed Checkmate. For local development: + +``` +http://localhost:52345/api/v1 +``` + +All endpoints are relative to this base. Every path on this page is shown without the `/api/v1` prefix — prepend it when making requests. + +## API stability + +The API is at version **`v1`** and is considered stable. Breaking changes will only ship under a new major version (`/api/v2`). + +## What you can do + +| Resource | What it covers | +|---|---| +| `monitors` | Create, update, pause, and delete monitors of any type (HTTP, Ping, Port, DNS, Docker, PageSpeed, gRPC, WebSocket, Game, Hardware) | +| `checks` | Read individual check results and aggregated stats | +| `incidents` | List, filter, and resolve downtime incidents | +| `notifications` | Manage notification channels (email, Slack, Discord, webhooks) | +| `status-page` | Configure public status pages | +| `maintenance-window` | Schedule maintenance windows so checks don't fire incidents | +| `geo-checks` | Multi-region check results | +| `auth` | Register, log in, manage users | +| `invite` | Invite teammates | +| `logs` | Audit and operational logs | +| `settings` | Tune global Checkmate settings | +| `queue` | Inspect the background-job queue (operational) | +| `diagnostic` | Health and system info | + +## What you'll need + +1. A running Checkmate instance you administer. +2. A user account on that instance. +3. A JWT obtained by calling `POST /auth/login` — see [Authentication](/api/authentication). + +There is no separate "API key" concept in Checkmate. The same JWT that authenticates the web UI authenticates every API call. + +## Response shape + +Every endpoint returns the same envelope: + +```json +{ + "success": true, + "msg": "OK", + "data": { /* endpoint-specific payload */ } +} +``` + +See [Conventions](/api/conventions) for the full contract and [Errors](/api/errors) for failure shapes. + +## Quick example + +```bash +# 1. Log in to get a token +TOKEN=$(curl -s -X POST https://your-checkmate-host/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"email":"you@example.com","password":"YourPass1!"}' \ + | jq -r '.data.token') + +# 2. List your team's monitors +curl https://your-checkmate-host/api/v1/monitors/team/ \ + -H "Authorization: Bearer $TOKEN" +``` + +Next: [Authentication](/api/authentication). diff --git a/docs/api-content/quickstart.md b/docs/api-content/quickstart.md new file mode 100644 index 000000000..fb9c36a4c --- /dev/null +++ b/docs/api-content/quickstart.md @@ -0,0 +1,117 @@ +--- +title: Quickstart +description: From zero to a working monitor in under five minutes. +--- + +# Quickstart + +This walkthrough creates a Checkmate user, logs in, provisions an HTTP monitor, and reads its checks — all via the API. + +It assumes you have a Checkmate instance running and reachable. Substitute `https://your-checkmate-host` with your actual host throughout. + +## 1. Register the first user + +If your instance is fresh, the first user becomes a superadmin: + +```bash +curl -X POST https://your-checkmate-host/api/v1/auth/register \ + -H "Content-Type: application/json" \ + -d '{ + "firstName": "Ada", + "lastName": "Lovelace", + "email": "ada@example.com", + "password": "Hunter2!Hunter2!", + "role": ["superadmin"], + "teamId": "" + }' +``` + +The response includes a JWT and a freshly-created `teamId`. Save both. + +If a superadmin already exists, skip to step 2 and use existing credentials. + +## 2. Log in + +If you already have an account: + +```bash +TOKEN=$(curl -s -X POST https://your-checkmate-host/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"email":"ada@example.com","password":"Hunter2!Hunter2!"}' \ + | jq -r '.data.token') + +TEAM_ID=$(curl -s -X POST https://your-checkmate-host/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"email":"ada@example.com","password":"Hunter2!Hunter2!"}' \ + | jq -r '.data.user.teamId') + +echo "Token: $TOKEN" +echo "Team: $TEAM_ID" +``` + +## 3. Create a monitor + +This creates an HTTP monitor that probes example.com every 60 seconds: + +```bash +curl -X POST https://your-checkmate-host/api/v1/monitors \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"example.com\", + \"type\": \"http\", + \"url\": \"https://example.com\", + \"interval\": 60000, + \"teamId\": \"$TEAM_ID\", + \"isActive\": true + }" +``` + +A successful response returns the new monitor including its `_id`. + +## 4. List your team's monitors + +```bash +curl https://your-checkmate-host/api/v1/monitors/team/$TEAM_ID \ + -H "Authorization: Bearer $TOKEN" +``` + +You should see the monitor you just created. Within a minute, the background worker will have run a few checks against it. + +## 5. Read recent checks + +Replace `` with the `_id` from step 3: + +```bash +curl "https://your-checkmate-host/api/v1/checks/?limit=10" \ + -H "Authorization: Bearer $TOKEN" +``` + +You'll see an array of check results — status code, response time, timestamp, region (if applicable). + +## 6. Pause and delete the monitor + +Pause: + +```bash +curl -X PATCH https://your-checkmate-host/api/v1/monitors/ \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"isActive": false}' +``` + +Delete: + +```bash +curl -X DELETE https://your-checkmate-host/api/v1/monitors/ \ + -H "Authorization: Bearer $TOKEN" +``` + +## What's next + +- **Set up notifications** — `POST /notifications` to wire email / Slack / Discord / webhook channels, then attach them to monitors. +- **Build a status page** — `POST /status-page` to publish your monitors publicly. +- **Schedule maintenance** — `POST /maintenance-window` to silence checks during planned downtime. +- **Browse the full API reference** — see every endpoint with request/response schemas. + +See the [reference](/api/reference) for the complete list of endpoints. diff --git a/docs/api-content/rate-limiting.md b/docs/api-content/rate-limiting.md new file mode 100644 index 000000000..0c23eac43 --- /dev/null +++ b/docs/api-content/rate-limiting.md @@ -0,0 +1,83 @@ +--- +title: Rate limiting +description: Per-IP request limits, headers, and how to back off. +--- + +# Rate limiting + +Checkmate applies two per-IP rate limiters, both with a **60-second window**: + +| Limiter | Applies to | Limit | +|---|---|---| +| General | All requests under `/api/v1/*` | **600 requests / 60 seconds** | +| Auth-sensitive | `/auth/login`, `/auth/register`, `/auth/recovery/*`, `/status-page/:url/unlock` | **15 requests / 60 seconds** | + +The two limiters are **independent**. Auth-sensitive endpoints count against both buckets — you can hammer `/auth/login` 15 times in a minute before being rate-limited there, but a single client can never exceed 600 total requests per minute regardless of which endpoints are called. + +## Headers + +Every response includes the standard `RateLimit-*` headers so you can pace yourself proactively: + +``` +RateLimit-Limit: 600 +RateLimit-Remaining: 587 +RateLimit-Reset: 42 +``` + +| Header | Meaning | +|---|---| +| `RateLimit-Limit` | The cap for the current window. | +| `RateLimit-Remaining` | Requests left before you hit `429`. | +| `RateLimit-Reset` | Seconds until the window resets. | + +## When you exceed the limit + +You receive `HTTP 429 Too Many Requests`: + +```json +{ + "success": false, + "msg": "Too many requests" +} +``` + +The response also includes `Retry-After`: + +``` +Retry-After: 42 +``` + +Wait that many seconds before retrying. If `Retry-After` is missing, fall back to the `RateLimit-Reset` header. + +## Recommended backoff + +For one-off scripts, simple sleep-then-retry is fine: + +```javascript +async function callWithRetry(url, opts, attempts = 3) { + for (let i = 0; i < attempts; i++) { + const res = await fetch(url, opts); + if (res.status !== 429) return res; + const retry = Number(res.headers.get("Retry-After") ?? 60); + await new Promise(r => setTimeout(r, retry * 1000)); + } + throw new Error("Rate limit retry exhausted"); +} +``` + +For higher-throughput integrations, watch `RateLimit-Remaining` and pace your sender to stay below the limit rather than waiting for `429`. + +## Limits and proxies + +Rate-limiting is per **client IP**, derived from the connection — or, when Checkmate is behind a reverse proxy with `trust proxy` configured, from the leftmost public IP in `X-Forwarded-For`. + +If many of your clients share an egress IP (NAT, corporate proxy), they share the bucket. In that case, an operator should either: + +- Increase the limits (server code change required), or +- Make sure each client connects from a distinct IP. + +## Limits are not contractual + +The numbers above reflect the current defaults. An operator can change them at any time (they're hardcoded in `server/src/middleware/rateLimiter.ts`). If you're building an integration that depends on a higher limit, coordinate with the operator first. + +Next: [API reference](/api/reference) — auto-generated from the OpenAPI spec. diff --git a/docs/api/.gitignore b/docs/api/.gitignore new file mode 100644 index 000000000..101d4adf2 --- /dev/null +++ b/docs/api/.gitignore @@ -0,0 +1,16 @@ +# Astro build +dist/ +.astro/ + +# Dependencies +node_modules/ + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Env +.env +.env.production diff --git a/docs/api/astro.config.mjs b/docs/api/astro.config.mjs new file mode 100644 index 000000000..7840dea8a --- /dev/null +++ b/docs/api/astro.config.mjs @@ -0,0 +1,74 @@ +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; +import starlightOpenAPI, { openAPISidebarGroups } from "starlight-openapi"; + +// The OpenAPI spec lives in the server package. The Starlight integration +// reads it at build time and generates the full /api/reference/* tree. +const OPENAPI_PATH = "../../server/openapi.json"; + +export default defineConfig({ + // Served at https://checkmate.so/docs — the leading slash is included so + // every internal link resolves correctly when fronted by a reverse proxy. + base: "/docs", + site: "https://checkmate.so", + + integrations: [ + starlight({ + title: "Checkmate API", + description: "Use the Checkmate REST API to manage monitors, incidents, status pages, notifications, and users programmatically.", + + // Prose markdown is loaded from /docs/api-content via the custom + // docsLoader in src/content.config.ts. Keeping content outside the + // Astro project tree means contributors can edit it without dealing + // with Astro / Starlight tooling. + + logo: { + src: "./src/assets/logo.svg", + replacesTitle: true, + }, + favicon: "/favicon.svg", + customCss: ["./src/styles/checkmate.css"], + + social: { + github: "https://github.com/bluewave-labs/Checkmate", + }, + + sidebar: [ + { + label: "Getting started", + items: [ + { label: "Introduction", slug: "introduction" }, + { label: "Quickstart", slug: "quickstart" }, + { label: "Authentication", slug: "authentication" }, + ], + }, + { + label: "Core concepts", + items: [ + { label: "Conventions", slug: "conventions" }, + { label: "Errors", slug: "errors" }, + { label: "Rate limiting", slug: "rate-limiting" }, + ], + }, + { + label: "API reference", + items: [ + { label: "Overview", slug: "api-reference/overview" }, + // The OpenAPI plugin appends every endpoint under this group. + ...openAPISidebarGroups, + ], + }, + ], + + plugins: [ + starlightOpenAPI([ + { + base: "api-reference", + label: "REST API", + schema: OPENAPI_PATH, + }, + ]), + ], + }), + ], +}); diff --git a/docs/api/package.json b/docs/api/package.json new file mode 100644 index 000000000..10e512933 --- /dev/null +++ b/docs/api/package.json @@ -0,0 +1,20 @@ +{ + "name": "checkmate-docs-api", + "type": "module", + "version": "0.1.0", + "private": true, + "description": "Public API documentation for Checkmate. Built with Astro Starlight.", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/starlight": "^0.30.0", + "astro": "^5.0.0", + "sharp": "^0.33.5", + "starlight-openapi": "^0.13.0" + } +} diff --git a/docs/api/src/assets/logo.svg b/docs/api/src/assets/logo.svg new file mode 100644 index 000000000..a3b394dab --- /dev/null +++ b/docs/api/src/assets/logo.svg @@ -0,0 +1,5 @@ + + + + Checkmate + diff --git a/docs/api/src/content.config.ts b/docs/api/src/content.config.ts new file mode 100644 index 000000000..89d310d81 --- /dev/null +++ b/docs/api/src/content.config.ts @@ -0,0 +1,13 @@ +import { defineCollection } from "astro:content"; +import { docsLoader } from "@astrojs/starlight/loaders"; +import { docsSchema } from "@astrojs/starlight/schema"; + +// Load markdown from a sibling folder so prose lives outside the Astro +// project tree. Contributors edit /docs/api-content/*.md without touching +// the build tool. +export const collections = { + docs: defineCollection({ + loader: docsLoader({ base: "../api-content" }), + schema: docsSchema(), + }), +}; diff --git a/docs/api/src/styles/checkmate.css b/docs/api/src/styles/checkmate.css new file mode 100644 index 000000000..530e5b590 --- /dev/null +++ b/docs/api/src/styles/checkmate.css @@ -0,0 +1,23 @@ +/* Checkmate brand colors layered on top of Starlight defaults. */ +:root { + --sl-color-accent-low: #0e5a48; + --sl-color-accent: #13715b; + --sl-color-accent-high: #1a8a6f; + + --sl-color-white: #ffffff; + --sl-color-gray-1: #eef2f0; + --sl-color-gray-2: #c9d3cf; + --sl-color-gray-3: #95a39d; + --sl-color-gray-4: #5c6d66; + --sl-color-gray-5: #3a4844; + --sl-color-gray-6: #2a3531; + --sl-color-black: #1a201e; +} + +/* Light mode tweaks so the green accent reads against white. */ +:root[data-theme="light"] { + --sl-color-accent-low: #d0e4dc; + --sl-color-accent: #13715b; + --sl-color-accent-high: #0e5a48; + --sl-color-text-accent: #0e5a48; +} diff --git a/docs/api/tsconfig.json b/docs/api/tsconfig.json new file mode 100644 index 000000000..8bf91d3bb --- /dev/null +++ b/docs/api/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +} diff --git a/docs/api/vercel.json b/docs/api/vercel.json new file mode 100644 index 000000000..c0747fe64 --- /dev/null +++ b/docs/api/vercel.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "npm run build", + "outputDirectory": "dist", + "framework": "astro", + "installCommand": "npm install" +}