{"id":31778391,"url":"https://github.com/fluential/ttr.rip","last_synced_at":"2025-10-10T06:46:24.895Z","repository":{"id":315682753,"uuid":"1060444626","full_name":"fluential/ttr.rip","owner":"fluential","description":"A simple health check monitoring service.","archived":false,"fork":false,"pushed_at":"2025-09-20T02:00:21.000Z","size":481,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2025-10-10T04:26:25.982Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fluential.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-19T23:40:56.000Z","updated_at":"2025-09-20T02:00:25.000Z","dependencies_parsed_at":"2025-09-20T04:08:17.383Z","dependency_job_id":"0d591e54-841d-4da6-bf3f-1ec4668c63c3","html_url":"https://github.com/fluential/ttr.rip","commit_stats":null,"previous_names":["fluential/ttr.rip"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/fluential/ttr.rip","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluential%2Fttr.rip","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluential%2Fttr.rip/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluential%2Fttr.rip/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluential%2Fttr.rip/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fluential","download_url":"https://codeload.github.com/fluential/ttr.rip/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluential%2Fttr.rip/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002972,"owners_count":26083489,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-10T06:46:21.260Z","updated_at":"2025-10-10T06:46:24.889Z","avatar_url":"https://github.com/fluential.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Authentication\nHow Authentication Works (Overall Flow)\n\nThe application has two distinct authentication mechanisms:\n\n 1 API Authentication (JWT-based): This is the primary, secure method used by the frontend JavaScript to communicate with the backend API (/api/v1/*).\n 2 Web UI Authentication (Cookie-based): This is a simpler mechanism used only to control access to the HTML dashboard page itself.\n\nThe flow for the API is as follows:\n\n 1 When the dashboard page (/) loads, the JavaScript in dashboard.html immediately calls the loginAndGetToken() function.\n 2 This function sends a POST request to /api/v1/token with the hardcoded credentials username: 'admin' and password: 'password'.\n 3 The /api/v1/token endpoint (in app/api/v1/endpoints/login.py) verifies these credentials against the user in the database.\n 4 If the credentials are correct, it generates a JSON Web Token (JWT) and returns it to the browser.\n 5 The JavaScript stores this JWT in a variable (apiToken).\n 6 For all subsequent API requests (like fetching or creating checks), the JavaScript includes this token in the Authorization header, like so: Authorization: Bearer \u003cthe_jwt_token\u003e.\n 7 The API endpoints for checks (in app/api/v1/endpoints/checks.py) are protected and use a dependency to validate this token on every request.\n\nHow JWT is Used and Validated\n\nJWT Creation:\n\n • The creation happens in app/security.py within the create_access_token function.\n • When a user logs in successfully via the /api/v1/token endpoint, this function is called.\n • It creates a Python dictionary (the \"payload\") containing the user's username (as sub, a standard JWT claim for \"subject\") and an expiration timestamp (exp).\n • It then uses the jose.jwt.encode() method to sign this payload. The signing process uses the SECRET_KEY and the ALGORITHM (HS256) defined in app/core/config.py.\n • The result is the compact, signed JWT string that is sent back to the client.\n\nJWT Validation:\n\n • Validation happens in app/security.py inside the get_current_user function, which acts as a FastAPI dependency.\n • Protected API endpoints, like read_checks in app/api/v1/endpoints/checks.py, include this function in their signature: current_user: db_models.User = Depends(security.get_current_user).\n • FastAPI automatically extracts the token from the Authorization: Bearer ... header.\n • The get_current_user function then uses jose.jwt.decode() to verify and decode the token. This process uses the same SECRET_KEY and ALGORITHM to check the token's signature and ensure it hasn't been tampered\n   with. It also automatically checks if the token has expired.\n • If the token is valid, the function extracts the username from the payload, fetches the corresponding user from the database, and returns the user object.\n • If the token is invalid, expired, or the signature doesn't match, a 401 Unauthorized HTTP exception is raised, and the request is denied.\n\nWhere the Validation Keys are Stored\n\nThe application uses a symmetric algorithm (HS256), which means it uses a single secret key for both signing and validating tokens, not a public/private key pair.\n\nThis secret key is managed in app/core/config.py:\n\n```python\n# app/core/config.py\n\nclass Settings(BaseSettings):\n    # ...\n    SECRET_KEY: str = \"a_very_secret_key\"\n    ALGORITHM: str = \"HS256\"\n    # ...\n\n    model_config = SettingsConfigDict(env_file=\".env\")\n\nsettings = Settings()\n```\n\nThe value is loaded from environment variables. It has a default value of \"a_very_secret_key\" for development but is intended to be overridden in production by setting a SECRET_KEY environment variable or placing\nit in a .env file, as shown in .env.example.\n# ttr.rip — Simple, resilient health‑check monitoring\n\nttr.rip is a FastAPI-based uptime and job monitoring service with anonymous key-based access, a clean, themeable UI, and robust Redis/Celery-powered background processing. It’s easy to run on a single node and durable enough for production, featuring adaptive rate control, flapping suppression, and Prometheus metrics out of the box.\n\n- Elegant web UI with multiple themes (Cyberpunk, Retro, Blueprint, Terminal, Solarized, Arcade)\n- Anonymous “access key” login, optional “Login with Telegram”\n- Public status pages with shareable badges\n- Telegram, Slack, Discord, and generic Webhook notifications\n- Adaptive rate control (AIMD + backoff) and flapping detection\n- Prometheus metrics and operational summary APIs\n- Docker-first deployment (Postgres, Redis, web, worker, beat, Caddy)\n\n---\n\n## Table of contents\n\n- [Features](#features)\n- [Screenshots](#screenshots)\n- [Quickstart (Docker)](#quickstart-docker)\n- [Configuration](#configuration)\n- [Running locally (without Docker)](#running-locally-without-docker)\n- [Concepts \u0026 Architecture](#concepts--architecture)\n- [API overview](#api-overview)\n- [Admin \u0026 Security model](#admin--security-model)\n- [Observability \u0026 Metrics](#observability--metrics)\n- [Background processing](#background-processing)\n- [Cleanup \u0026 Lifecycle](#cleanup--lifecycle)\n- [Import/Export](#importexport)\n- [Development](#development)\n- [Roadmap](#roadmap)\n- [License](#license)\n\n---\n\n## Features\n\nUser \u0026 Auth\n- Anonymous key-based access via cookie or X-Auth-Key header\n- Optional “Login with Telegram”\n- Per-user slug for clean ping URLs and public pages\n- CSRF protection for forms and APIs (Double Submit Cookie pattern)\n\nChecks\n- Scheduling: interval, cron, or systemd OnCalendar\n- Durable status in DB: up / down / new, with last_ping, last_start, last_duration_seconds\n- Deadlines and grace windows computed/persisted in DB (reliable overdue detection)\n- Optional content validation (present/absent or regex) on ping payloads\n- Pause/resume with correct counters and metrics updates\n- Cursor-based pagination, ETag’d list/aggregate responses\n\nIntegrations \u0026 Alerts\n- Telegram, Slack, Discord, and generic Webhook\n- Adaptive global rate control (AIMD + exponential backoff), cross-worker via Redis\n- Flapping detection with suppression windows\n- Test flows: immediate send or queue-based\n\nStatus Pages\n- Public pages under /s/{user_slug}/{page_slug}\n- Layouts: cards, grid, timeline\n- Safe “recent activity” with country code/name and connection hints\n- Badge endpoint: /p/{user_slug}/{check_id_or_slug}/badge.svg\n\nUI \u0026 Theming\n- Multiple themes, light/dark mode, persisted user preference\n- Compact, accessible dashboard with inline actions and quick copy\n- Real-time feel with periodic refresh, countdowns, and subtle glow indicators\n\nObservability\n- Prometheus metrics (/metrics, admin-only)\n- Summary API (/api/v1/metrics/summary) with ETag caching\n- Cross-worker latency aggregation in Redis\n- Worker heartbeats and “workers online” gauge\n\nPerformance \u0026 Resilience\n- Redis-backed runtime hints (e.g., last_content, recent pings)\n- Buffered Redis HINCRBY with coalesced flushes\n- Fail-open design on metrics and cache paths\n\nMaintenance\n- Periodic cleanup of long-inactive checks/users (configurable)\n- Alembic migrations\n- Import/export checks as JSON\n\n---\n\n## Screenshots\n\n- Dashboard: user checks, status counters, pagination, quick actions\n- Integrations: per-check settings and live rate snapshots\n- Public status pages: cards/grid/timeline views\n\n(See app/web/templates and app/static/css/themes for layouts and styles.)\n\n---\n\n## Quickstart (Docker)\n\nRequirements:\n- Docker and docker-compose\n- A valid Fernet ENCRYPTION_KEY (32 url-safe base64-encoded bytes)\n\n1) Prepare environment\n- Copy the defaults and edit as needed:\n  cp .env.example .env\n- Generate a Fernet key and set ENCRYPTION_KEY in .env:\n  python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"\n\n2) Start the stack\n- Bring up Postgres, Redis, web, worker, beat, and Caddy:\n  docker-compose up -d --build\n\n3) Access\n- Via Caddy (recommended): http://localhost:8080\n- Direct FastAPI (dev): http://localhost:8000\n- Optional: Cloudflare Tunnel\n  - Set CLOUDFLARED_TOKEN in .env to attach to a named Tunnel (hostnames managed under Cloudflare Zero Trust → Tunnels → Public Hostnames, e.g. status.example.com → http://caddy:8080).\n  - Leave CLOUDFLARED_TOKEN empty to start a temporary Quick Tunnel (random trycloudflare.com URL).\n  - cloudflared forwards to http://caddy:8080 by default; override with CLOUDFLARED_URL. Extra flags via CLOUDFLARED_OPTS.\n  - Compatibility: you can also set CLOUDFLARE_TUNNEL_TOKEN (alias used by community examples); compose prefers CLOUDFLARED_TOKEN, then CLOUDFLARE_TUNNEL_TOKEN.\n\n4) Get a key\n- Click “Get a New Key” to obtain an access key, then go to /dashboard.\n- Your key is stored in an HttpOnly cookie; keep it safe.\n\nAdmin UI\n- Create an admin user (see “Admin \u0026 Security model”).\n- Visit http://localhost:8080/admin/login\n\nStop \u0026 logs\n- Stop: docker-compose down\n- Logs: docker-compose logs -f web (or worker/beat)\n\n---\n\n## Configuration\n\nSettings live in app/core/config.py and are overridden by .env (see .env.example):\n\nCore\n- DATABASE_URL: DB DSN (Postgres recommended)\n- REDIS_URL: Redis broker/result and cache\n- SECRET_KEY: JWT signing secret\n- ENCRYPTION_KEY: Fernet key (required) for encrypting user secrets\n- DEBUG_MODE: If true, Celery tasks run eagerly (no broker required)\n\nScheduling \u0026 Worker\n- SCHEDULER_INTERVAL_SECONDS\n- AUTO_START_EMBEDDED_WORKER, WORKER_CONCURRENCY\n- EMBED_BEAT (use worker -B or separate beat service)\n\nRedis Counters Buffer\n- INCR_BUFFER_ENABLED, INCR_BUFFER_FLUSH_INTERVAL_MS, INCR_BUFFER_MAX_OPS\n\nTelegram\n- TELEGRAM_AUTH_ENABLED, TELEGRAM_BOT_NAME, TELEGRAM_BOT_TOKEN\n\nCleanup\n- CLEANUP_ENABLED, CLEANUP_INACTIVE_DAYS, CLEANUP_INTERVAL_HOURS\n\nSecurity \u0026 CSP\n- USER_SLUG_ENABLED\n- XAUTH_ENFORCE_ORIGIN, XAUTH_ENFORCE_IP\n- CSP_USER_DASHBOARD (Content-Security-Policy for user-facing pages)\n\nGeoIP \u0026 Logging\n- SAVE_CHECK_LAST_LOGS\n- GEOIP_DATABASE_PATH (optional)\n- LOG_LEVEL, UVICORN_LOG_LEVEL\n\nMost features degrade gracefully if Redis is absent or DEBUG_MODE is enabled.\n\n---\n\n## Running locally (without Docker)\n\n1) Install dependencies\n- Python 3.11+\n- Postgres and Redis running\n- pip install -r requirements.txt\n\n2) Configure environment\n- cp .env.example .env\n- Set ENCRYPTION_KEY (Fernet key)\n\n3) Migrate DB\n- alembic upgrade head\n\n4) Run services\n- API (dev): uvicorn app.main:app --reload\n- Worker: celery -A app.worker.celery_app worker --loglevel=info -P solo\n- Beat (if not embedded): celery -A app.worker.celery_app beat --loglevel=info\n\nOpen http://localhost:8000\n\n---\n\n## Concepts \u0026 Architecture\n\n- FastAPI application (app/main.py) serving:\n  - Public pages (/, /dashboard, /check/{id}/integrations)\n  - Public status pages (/s/{user_slug}/{page_slug})\n  - REST APIs under /api/v1\n  - Admin SPA endpoints (/admin/*)\n- Database (SQLAlchemy + Alembic): Users, Checks, Tags, StatusPages\n- Redis:\n  - Celery broker/result backend\n  - Runtime cache (recent pings, last content)\n  - Global counters and latency aggregation\n  - Worker heartbeats\n- Celery worker (app/worker.py):\n  - Notification tasks (Telegram/Slack/Discord/Webhook)\n  - Periodic overdue/long-running detection (Beat)\n- Adaptive rate control (app/services/rate_control.py):\n  - AIMD refill, min drip, exponential backoff\n  - Per-identity state (e.g., sha256(token)[:10])\n- Alerting (app/services/alerting.py):\n  - Flapping detection with suppression TTL\n\nData flow examples\n- Ping endpoint (/p/{user_slug}/{check_identifier}):\n  - Optionally logs geo-hints and UA to Redis\n  - Validates content, updates DB state and deadlines\n  - Schedules notifications (with adaptive rate + retries)\n- Metrics:\n  - /metrics for Prometheus\n  - /api/v1/metrics/summary for UI (ETag-cached JSON)\n\n---\n\n## API overview\n\nPublic (X-Auth-Key)\n- GET /api/v1/checks\n  - Query params: size, sort_by, sort_direction, cursor, tag\n  - ETag’d responses, cursor pagination\n- GET /api/v1/checks/aggregate\n  - One-call dashboard aggregate (checks + stats + metrics + tags)\n- GET /api/v1/checks/stats\n- POST /api/v1/checks\n- PUT /api/v1/checks/{check_id}\n- DELETE /api/v1/checks/{check_id}\n- PUT /api/v1/checks/{id}/{integration} (telegram|slack|discord|webhook)\n- POST /api/v1/checks/{id}/{integration}/test (immediate)\n- POST /api/v1/checks/{id}/{integration}/test-queue (enqueue)\n- GET /api/v1/checks/{id}/{integration}/rate (live rate snapshot)\n- Status pages: /api/v1/status-pages (CRUD)\n- Import/Export:\n  - GET /api/v1/checks/export\n  - POST /api/v1/checks/import\n- Pings:\n  - /p/{user_slug}/{check_identifier} (GET/POST)\n  - /p/{user_slug}/{check_identifier}/start\n  - /p/{user_slug}/{check_identifier}/fail\n  - /p/{user_slug}/{check_identifier}/badge.svg\n\nAdmin (JWT)\n- GET /api/v1/admin/stats\n- GET /metrics (Prometheus, admin-only)\n\nPublic Pages\n- GET /s/{user_slug}/{page_slug}\n- GET /s/{user_slug}/{page_slug}/data\n\n---\n\n## Admin \u0026 Security model\n\n- Admin users:\n  - Username/password -\u003e short-lived access token (Bearer) + HttpOnly refresh cookie\n  - SPA flow with token refresh (CSRF-protected)\n- Public users:\n  - X-Auth-Key via cookie/header\n  - Optional Telegram binding\n  - Blacklisting: keys can be temporarily blacklisted in Redis\n- CSRF:\n  - Double Submit Cookie pattern for forms/APIs\n- CSP:\n  - Strict CSP applied to user-facing pages (configurable)\n\n---\n\n## Observability \u0026 Metrics\n\n- Prometheus endpoint: /metrics (admin-only)\n- Metrics summary API: /api/v1/metrics/summary\n  - Totals (checks, users, notifications)\n  - Average latencies (API/DB/Redis/queue), queue depth\n  - Health colors for quick at-a-glance\n- Redis-based cross-worker aggregation for accurate averages\n- Worker heartbeats in Redis to compute “workers online”\n\n---\n\n## Background processing\n\n- Celery worker tasks:\n  - Notifications with retries and RateLimitedError handling\n  - Overdue checks and long-running detection (Beat every few seconds)\n- Heartbeats:\n  - metrics:workers_online:set + per-worker TTL keys\n- Eager mode in DEBUG (no broker required)\n\n---\n\n## Cleanup \u0026 Lifecycle\n\n- Periodic cleanup (app/tasks/cleanup.py) when enabled:\n  - Deletes long-inactive checks\n  - Deletes users without active checks and no Telegram linkage\n  - Best-effort Redis cleanup of related keys\n- Manual run:\n  - python app/commands/cleanup_cmd.py\n\n---\n\n## Import/Export\n\n- Export:\n  - GET /api/v1/checks/export → JSON list (includes integration flags/urls)\n- Import:\n  - POST /api/v1/checks/import → accepts same format\n  - Secrets are re-encrypted using the current user’s auth key\n\n---\n\n## Development\n\n- Stack:\n  - FastAPI, SQLAlchemy (async), Alembic\n  - Redis asyncio client with pooled connections\n  - Celery (Redis broker/result), Prometheus client\n- Useful entry points:\n  - app/main.py (FastAPI app, routes mounting)\n  - app/api/v1/endpoints/* (REST endpoints)\n  - app/web/* (templates and routes)\n  - app/services/* (notifications, alerting, rate control, queue stats)\n  - app/worker.py (Celery config, periodic tasks)\n  - app/db/models.py (ORM models)\n- Local dev:\n  - uvicorn app.main:app --reload\n  - celery -A app.worker.celery_app worker --loglevel=info -P solo\n  - celery -A app.worker.celery_app beat --loglevel=info\n- Code style:\n  - Use your preferred formatters/linters (e.g., black/ruff/mypy)\n\n---\n\n## Roadmap\n\n- More integrations (email/SMS gateways)\n- Quotas/rate-plans and richer admin controls\n- Secret backends (KMS/HSM adapters)\n- Multi-region setups and sharding options\n- Deeper analytics and dashboards\n\n---\n\n## License\n\nLicensed under the terms of the LICENSE file in this repository.\n\n---\n\n## Developer quickstart: curl and API usage\n\nThis section shows how to interact with ttr.rip over HTTP using curl. You can use these patterns to build simple scripts or SDKs.\n\nEnvironment setup\n- BASE is the base URL for your deployment.\n- AUTH_KEY is your anonymous access key (from the UI “Get a New Key” or your cookie).\n- ADMIN_TOKEN is a short‑lived JWT for admin APIs.\n\n```bash\n# Public base URL (examples assume local dev)\nBASE=http://localhost:8000\n\n# Public auth: use your X‑Auth‑Key for public endpoints\n# Replace with your actual key (32 url-safe chars); do not share it publicly.\nAUTH_KEY=\"YOUR_PUBLIC_AUTH_KEY\"\n\n# Admin auth: exchange username/password for a JWT\nADMIN_TOKEN=$(curl -s -X POST -d \"username=admin\u0026password=password\" \"$BASE/api/v1/token\" | jq -r '.access_token')\n```\n\nNotes\n- Public APIs: send X-Auth-Key: \u003cAUTH_KEY\u003e header.\n- Admin APIs: send Authorization: Bearer \u003cADMIN_TOKEN\u003e header.\n- Time fields are ISO 8601 (UTC). Status values: up | down | new | paused.\n\n### Checks API (public)\n\nList checks (paginated):\n```bash\ncurl -s -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks?size=10\u0026sort_by=id\u0026sort_direction=desc\" | jq .\n```\n\nCreate a check (interval schedule):\n```bash\ncurl -s -X POST \"$BASE/api/v1/checks\" \\\n  -H \"X-Auth-Key: $AUTH_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"My Job\",\"schedule_type\":\"interval\",\"interval_seconds\":60,\"grace_seconds\":30}' | jq .\n```\n\nUpdate a check:\n```bash\ncurl -s -X PUT \"$BASE/api/v1/checks/123\" \\\n  -H \"X-Auth-Key: $AUTH_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"My Job (renamed)\",\"schedule_type\":\"interval\",\"interval_seconds\":120,\"grace_seconds\":30}' | jq .\n```\n\nDelete a check:\n```bash\ncurl -s -X DELETE -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/123\" -i\n```\n\nExport all checks:\n```bash\ncurl -s -H \"X-Auth-Key: $AUTH_KEY\" -H \"Accept: application/json\" \"$BASE/api/v1/checks/export\" -o ttr_rip_checks_export.json\n```\n\nImport checks (from a file produced by export):\n```bash\ncurl -s -X POST \"$BASE/api/v1/checks/import\" \\\n  -H \"X-Auth-Key: $AUTH_KEY\" \\\n  -F \"file=@ttr_rip_checks_export.json\" | jq .\n```\n\nGet last content captured for a check:\n```bash\ncurl -s -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/123/content\" | jq .\n```\n\nToggle pause:\n```bash\ncurl -s -X POST -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/123/toggle-pause\" | jq .\n```\n\nCheck slug availability:\n```bash\ncurl -s -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/slug-check?slug=my-slug\" | jq .\n```\n\nTags for your checks:\n```bash\ncurl -s -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/tags\" | jq .\n```\n\nUser stats (counts, averages):\n```bash\ncurl -s -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/stats\" | jq .\n```\n\n### Integrations per check (public)\n\nUpdate Telegram settings:\n```bash\ncurl -s -X PUT \"$BASE/api/v1/checks/123/telegram\" \\\n  -H \"X-Auth-Key: $AUTH_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"telegram_enabled\":true,\"telegram_chat_id\":\"123456789\",\"telegram_bot_token\":\"1234:abcd\"}' | jq .\n```\n\nSend test immediately / via queue:\n```bash\ncurl -s -X POST -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/123/telegram/test\" | jq .\ncurl -s -X POST -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/123/telegram/test-queue\" | jq .\n```\n\nLive rate snapshot (AIMD/backoff):\n```bash\ncurl -s -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/checks/123/telegram/rate\" | jq .\n```\n\nSlack/Discord/Webhook endpoints are analogous:\n- PUT /api/v1/checks/{id}/slack\n- PUT /api/v1/checks/{id}/discord\n- PUT /api/v1/checks/{id}/webhook\n- POST /api/v1/checks/{id}/{integration}/test\n- POST /api/v1/checks/{id}/{integration}/test-queue\n- GET /api/v1/checks/{id}/{integration}/rate\n\n### Status pages (public)\n\nList pages:\n```bash\ncurl -s -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/status-pages\" | jq .\n```\n\nCreate/update/delete:\n```bash\ncurl -s -X POST \"$BASE/api/v1/status-pages\" \\\n  -H \"X-Auth-Key: $AUTH_KEY\" -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Prod\",\"slug\":\"prod\",\"check_ids\":[1,2,3]}' | jq .\n\ncurl -s -X PUT \"$BASE/api/v1/status-pages/10\" \\\n  -H \"X-Auth-Key: $AUTH_KEY\" -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Prod\",\"slug\":\"prod\",\"check_ids\":[1,3]}' | jq .\n\ncurl -s -X DELETE -H \"X-Auth-Key: $AUTH_KEY\" \"$BASE/api/v1/status-pages/10\" -i\n```\n\nPublic page and data feed:\n```bash\n# HTML\ncurl -s \"$BASE/s/{user_slug}/{page_slug}\" -i\n# JSON feed (etagged, 2s buckets)\ncurl -s \"$BASE/s/{user_slug}/{page_slug}/data\" | jq .\n```\n\n### Pings and badges (public)\n\nSend a ping to your check:\n```bash\n# GET-based ping\ncurl -s \"$BASE/p/{user_slug}/{check_identifier}?ok=1\"\n\n# POST payload ping\ncurl -s -X POST \"$BASE/p/{user_slug}/{check_identifier}\" \\\n  -H \"Content-Type: text/plain\" \\\n  --data-binary 'hello from cron'\n```\n\nBadge:\n```bash\ncurl -s \"$BASE/p/{user_slug}/{check_identifier}/badge.svg\" -o badge.svg\n```\n\n### Admin APIs\n\nExchange credentials for a JWT:\n```bash\nADMIN_TOKEN=$(curl -s -X POST -d \"username=admin\u0026password=password\" \"$BASE/api/v1/token\" | jq -r '.access_token')\n```\n\nSystem stats:\n```bash\ncurl -s -H \"Authorization: Bearer $ADMIN_TOKEN\" \"$BASE/api/v1/admin/stats\" | jq .\n```\n\nPrometheus metrics (admin-only):\n```bash\ncurl -s -H \"Authorization: Bearer $ADMIN_TOKEN\" \"$BASE/metrics\"\n```\n\nOperational metrics summary (public read):\n```bash\ncurl -s \"$BASE/api/v1/metrics/summary\" | jq .\n```\n\n### SDK tips\n\n- Authentication\n  - Public: X-Auth-Key in header; cookie is used by the web UI but not required for APIs.\n  - Admin: Authorization: Bearer \u003ctoken\u003e.\n- IDs vs slugs\n  - Checks can be addressed by numeric ID in APIs, and by slug or UUID in ping URLs.\n- Rate control\n  - Notification senders are throttled with AIMD/backoff; 429s are handled internally. Rate snapshots expose state you can surface to users.\n- ETags and caching\n  - Many list endpoints provide weak ETags with short max-age to balance freshness and load.\n- Error handling\n  - Validation errors return 400 with a detail message; missing resources return 404; unauthorized returns 401.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluential%2Fttr.rip","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffluential%2Fttr.rip","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluential%2Fttr.rip/lists"}