Skip to main content

Self-hosted API health monitoring with AI-explained failures — runs in your own cron/CI, never on our servers

Project description

oneport-apiwatch

Self-hosted API health monitoring with AI-explained failures. It runs in your cron or CI, on your keys — never on our servers.

UptimeRobot and Checkly watch your APIs from someone else's cloud. oneport-apiwatch is a single stateless command you schedule in your own GitHub Actions / cron. It probes your endpoints, and when something breaks it explains why in plain English and alerts your Slack — with zero infrastructure on our side and your data never leaving your machine.

✗ payments  POST https://api.acme.com/pay  [500 · 1840ms]
      → status 500 (expected 200)
      → latency 1840ms over budget 1000ms
      AI diagnosis: 500s with "connection pool exhausted" and latency climbing
      over the last 3 runs point to a database connection leak.
        1. DB connection pool exhausted
        2. A recent deploy stopped releasing connections
        first action: Check the last deploy and your DB pool metrics.

Monitoring without a server (the headline)

The whole product is one idea: let GitHub's scheduler be the uptime robot.

  1. Drop a checks file (oneport-apiwatch.yaml) and a scheduled workflow into your repo.
  2. GitHub Actions wakes every 5 minutes, runs oneport-apiwatch check, and the job goes red if any endpoint is down.
  3. On failure it explains the cause (your Gemini key) and alerts your Slack / opens an issue in your repo.

No server of ours sits in the path. You own the schedule, the keys, the alerts, and the history file.

# .github/workflows/apiwatch.yml
name: API Watch
on:
  schedule:
    - cron: "*/5 * * * *"     # GitHub's scheduler is your uptime robot
  workflow_dispatch: {}
permissions:
  contents: read
  issues: write
jobs:
  watch:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install oneport-apiwatch
      - uses: actions/cache@v4        # keep status history across runs for trend detection
        with:
          path: .oneport-apiwatch-history.json
          key: apiwatch-history-${{ github.run_id }}
          restore-keys: apiwatch-history-
      - env:
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          oneport-apiwatch check --explain --alert slack --alert github-issue

The complete, commented version is in examples/workflows/apiwatch.yml. Prefer plain cron? It's the same one line:

*/5 * * * *  cd /srv/checks && oneport-apiwatch check --explain --alert slack

Install

pip install oneport-apiwatch

Python 3.10+. The deterministic check needs no API key at all — a Gemini key is only used for the optional --explain diagnosis (free at aistudio.google.com/apikey).

The checks file

Secrets never live in this file — auth headers are pulled from environment variables by name, so it's safe to commit.

model: gemini-2.5-flash            # optional; only used by --explain
history_file: .oneport-apiwatch-history.json

defaults:                          # applied to every check unless overridden
  method: GET
  expect_status: 200
  latency_budget_ms: 2000
  timeout: 10

checks:
  - name: homepage
    url: https://example.com/

  - name: api-health
    url: https://api.example.com/health
    expect_status: [200, 204]      # int, or a list of acceptable codes
    latency_budget_ms: 800
    headers:
      Accept: application/json
    body:                          # JSON-body assertions
      - path: status               # dot-path into the response (lists: data.items.0.id)
        equals: ok
      - path: db.connected
        equals: true
      - path: data.count
        gt: 0

  - name: billing
    url: https://api.example.com/v1/account
    auth_header_env: API_TOKEN     # value read from $API_TOKEN at runtime
    auth_header_name: Authorization

Body assertion operators: equals, not_equals, contains, exists, gt, lt (exactly one per assertion).

A check fails when any of these is true: transport error (DNS / refused / timeout), status not in expect_status, latency over latency_budget_ms, response isn't valid JSON while assertions are set, or any body assertion fails.

CLI

oneport-apiwatch check [OPTIONS]

  --config FILE                Path to the checks file (default: ./oneport-apiwatch.yaml).
  --alert [slack|github-issue] Alert this channel on a state change. Repeatable.
  --explain                    Add an AI diagnosis to each failure (needs GEMINI_API_KEY).
  --format [inline|json|prometheus]  Output format (default: inline).
  --no-history                 Don't read/write the local status-history file.

oneport-apiwatch auth          Show whether a Gemini key is configured.

Exit codes: 0 healthy (nothing tripped) · 1 one or more checks tripped the failure threshold · 2 config error · 3 a check failed and an alert failed to deliver.

Reliability — retries & flap tolerance

A single transient blip shouldn't page you. Configure per-check (or under defaults:):

defaults:
  retries: 2                 # retry a TRANSIENT failure (timeout / connection / 5xx) …
  retry_backoff_ms: 250      #   … waiting this long between attempts
  failure_threshold: 3       # only "trip" (fail the gate + alert) after 3 CONSECUTIVE
                             #   failed runs — ride out two blips. Default 1 = trip at once.
concurrency: 8               # probe up to N endpoints in parallel (results stay in order)

4xx, latency-budget, and failed-assertion results are deterministic and are never retried. Below the threshold a check shows as DEGRADED (n/3) and does not fail CI; at the threshold it's DOWN.

Maintenance mutes

checks:
  - name: reports-api
    url: https://example.com/reports
    muted: true                       # hard mute — never trips or alerts
    # or a window:
    mute_until: "2026-08-05T18:00:00Z"
    mute_reason: "planned migration"

A muted check is still probed and shown ([MUTED]) but excluded from the gate and alerts. An expired mute_until starts alerting again automatically.

Prometheus

--format prometheus emits gauges (apiwatch_up, apiwatch_latency_ms, apiwatch_status_code, apiwatch_consecutive_failures, apiwatch_tripped, apiwatch_muted) for a Grafana textfile scrape or a Pushgateway.

Alerting — your channels, not ours

Alerts fire on a state change, not every run: once when a check newly trips, and once when it recovers — no re-alerting every cron tick while it's down.

Channel Env it uses What it does
slack SLACK_WEBHOOK_URL POSTs a failure summary (with the AI diagnosis) when a check trips, and a recovery notice when it's healthy again.
github-issue GITHUB_REPOSITORY, GITHUB_TOKEN Opens — or updates, deduped by a hidden marker — one issue while checks are down, and comments + closes it on recovery.

Both use credentials you provide. Nothing routes through Oneport.

Trend detection

Each run appends {ok, status_code, latency_ms, timestamp} per check to a local JSON file you own (history_file). The --explain layer feeds the recent runs to the model, so it can distinguish "just went down" from "latency has been climbing for three runs." In CI, persist that file with actions/cache (see the workflow) so trends survive across scheduled runs.

Privacy / the moat

Serverless by design. Your endpoints are probed from your machine; the only outbound call besides the endpoints themselves is the optional Gemini request under --explain, made with your key. See PRIVACY.md.

Development

pip install -e ".[dev]"
pytest            # 83 tests, respx-mocked endpoints + LLM + Slack, 80% coverage gate

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oneport_apiwatch-0.2.0.tar.gz (37.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oneport_apiwatch-0.2.0-py3-none-any.whl (29.8 kB view details)

Uploaded Python 3

File details

Details for the file oneport_apiwatch-0.2.0.tar.gz.

File metadata

  • Download URL: oneport_apiwatch-0.2.0.tar.gz
  • Upload date:
  • Size: 37.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.0

File hashes

Hashes for oneport_apiwatch-0.2.0.tar.gz
Algorithm Hash digest
SHA256 63387739562c43d18d92a8a94f47dfda2745e465b3f35915cea636e60fea3249
MD5 3afe277de129ac82356802307559503a
BLAKE2b-256 4f8cc076949061f6f4a15f825f6770bfaad8e813bd47edb5b59405516e25dbd7

See more details on using hashes here.

File details

Details for the file oneport_apiwatch-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for oneport_apiwatch-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2cd400685eaf6df1dbdd679cd766dca44b58d5b72ef09cb4c1c7749d4d8ea59f
MD5 50115bf3909bbc2611a2b532ae154356
BLAKE2b-256 15a88d42a93b19db71772a056c7dd103ef194a436d4c2f5c3179d824ee0bbd79

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page