Skip to content
Docs/Quickstart for agents

Get started

Quickstart for agents

Everything here works without a human. You get a key from one unauthenticated call, monitor services, publish them on a status page, and hand your human a claim link when it suits you.

1. Get an API key

If your human already gave you a key (ub_live_…), skip ahead. Otherwise, bootstrap a workspace:

Bootstrap (no auth)
curl -X POST https://upbutler.com/api/v1/agent/bootstrap \
  -H "Content-Type: application/json" \
  -d '{"agentName": "claude-code", "workspaceName": "Acme production"}'
201 Created
{
  "workspace": {
    "id": "ws_0n3q8jy8r5t1y3u7i9o",
    "name": "Acme production",
    "plan": "free",
    "unclaimedUntil": "2026-10-16T09:12:44.120Z"
  },
  "apiKey": "ub_live_4k9xq2m7tz1v8c3n5w0r6y2h8j1p4d7f9s0a",
  "claimUrl": "https://upbutler.com/claim/clm_d2VsY29tZS10by11cGJ1dGxlci1jbGFpbQ",
  "next": [
    "Store apiKey securely; send it as \"Authorization: Bearer <apiKey>\".",
    "POST /api/v1/services {\"name\",\"url\"} to start monitoring a service and publish it on a status page.",
    "Share claimUrl with your human so they can own the workspace."
  ],
  "docs": "https://upbutler.com/docs/agents",
  "mcp": "https://upbutler.com/mcp"
}
  • apiKey has read and write scopes. Send it as Authorization: Bearer <apiKey> on every call. It is shown only once.
  • claimUrl: give this to your human. When they open it and sign in, they own the workspace. Unclaimed workspaces are deleted after 7 days (unclaimedUntil).
  • Until the workspace is claimed, email features stay locked: you can't create email alert channels. Webhook, Slack, Discord and Telegram channels work right away.
  • Bootstrapping is rate-limited to 5 workspaces per hour per IP address.

Check what your key can do and how much quota is left:

curl https://upbutler.com/api/v1/workspace -H "Authorization: Bearer $UPBUTLER_API_KEY"

2. Plug in a service

POST /services is the shortcut. It creates a monitor, creates the status page if it doesn't exist yet (statusPage is a slug, id or name), and adds a component that follows the monitor, all in one call.

Monitor + status page component
curl -X POST https://upbutler.com/api/v1/services \
  -H "Authorization: Bearer $UPBUTLER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Search API",
    "url": "https://api.example.com/health",
    "statusPage": "acme",
    "group": "APIs"
  }'
201 Created (abridged)
{
  "monitor": { "_id": "mon_0n3q8kz1m4hx7c2v9rt", "kind": "http", "state": "pending", "intervalSec": 300, ... },
  "component": { "_id": "cmp_0n3q8kz2p7wd4yx0s3a", "key": "search-api", "source": { "type": "monitor", "monitorIds": ["mon_0n3q8kz1m4hx7c2v9rt"], "aggregate": "worst" }, ... },
  "statusPage": { "_id": "pg_0n3q8kz0b2fd81mka5e", "slug": "acme", "url": "https://upbutler.com/s/acme", ... },
  "statusPageUrl": "https://upbutler.com/s/acme"
}

It accepts every monitor field, plus statusPage, group and componentName. Leave out statusPage to only monitor. The default check interval is 60 seconds, or the plan minimum if that is higher (300 seconds on Free).

3. Heartbeats for your own jobs

Monitoring a cron job, a queue worker or your own agent loop? Pass periodSec and you get a heartbeat monitor instead:

curl -X POST https://upbutler.com/api/v1/services \
  -H "Authorization: Bearer $UPBUTLER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Nightly backup", "periodSec": 86400, "graceSec": 1800, "statusPage": "acme", "group": "Jobs"}'
…the response includes
"heartbeat": {
  "pingUrl": "https://upbutler.com/hb/hb_Xf3kq9LmR2vT8wYzN4bC1dE7",
  "failUrl": "https://upbutler.com/hb/hb_Xf3kq9LmR2vT8wYzN4bC1dE7/fail",
  "example": "curl -fsS https://upbutler.com/hb/hb_Xf3kq9LmR2vT8wYzN4bC1dE7"
}

Call the pingUrl after every successful run, with no API key (the token is the secret). If no ping arrives within periodSec + graceSec, the monitor goes down. To report a failure yourself, hit failUrl: explicit failures alert right away, with no confirmation delay.

# success, with an optional message
curl -fsS "https://upbutler.com/hb/hb_Xf3kq9LmR2vT8wYzN4bC1dE7?msg=indexed+18k+docs"

# explicit failure: alerts immediately
curl -fsS -X POST https://upbutler.com/hb/hb_Xf3kq9LmR2vT8wYzN4bC1dE7/fail -d "disk full on worker-3"

More patterns (GitHub Actions, systemd, durations) are in Heartbeats.

4. Report what you notice

If you detect a problem before a monitor does, declare an incident. The listed components switch to the given status, page subscribers are notified, and with polish: true UpButler rewrites your rough notes into a calm public update.

curl -X POST https://upbutler.com/api/v1/incidents \
  -H "Authorization: Bearer $UPBUTLER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Search results are stale",
    "message": "indexer lagging ~40min behind, rebuilding shard 3",
    "polish": true,
    "components": [{ "componentId": "cmp_0n3q8kz2p7wd4yx0s3a", "status": "degraded" }]
  }'

Post progress with POST /incidents/:id/updates ("public": false makes it an internal note), and finish with POST /incidents/:id/resolve. If you leave out the resolve message, UpButler writes one, using the AI recovery summary when available. If you keep your own health data, push component statuses directly instead. See Push & manifest.

5. React to events

Option A: a webhook channel (your own workspace)

If you have a public HTTPS endpoint, create a webhook alert channel. Deliveries are signed per Standard Webhooks and retried with backoff for roughly 11 hours.

curl -X POST https://upbutler.com/api/v1/channels \
  -H "Authorization: Bearer $UPBUTLER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Ops agent", "type": "webhook", "url": "https://agent.example.com/hooks/upbutler"}'
# → { ..., "signingSecret": "whsec_..." }   (shown once, store it)

By default, a channel receives the alert events (monitor.down/up/degraded/reminder, incident.*, maintenance.*). Pass "events": ["*"] to get everything, including component.status_changed. Event types are listed in Events.

Option B: poll the event stream

No public endpoint? Poll GET /events with after=<last event id>. With after set, events come back oldest first, and next is the cursor for your next call.

poll.py
import os, time, requests

API = "https://upbutler.com/api/v1"
H = {"Authorization": f"Bearer {os.environ['UPBUTLER_API_KEY']}"}

# Start from the newest event so you only see what happens from now on.
latest = requests.get(f"{API}/events", headers=H, params={"limit": 1}).json()["data"]
cursor = latest[0]["id"] if latest else None

while True:
    params = {"types": "monitor.down,monitor.up,incident.created,incident.resolved"}
    if cursor:
        params["after"] = cursor
    page = requests.get(f"{API}/events", headers=H, params=params).json()
    for event in page["data"]:          # oldest first when "after" is set
        print(event["type"], event["data"].get("monitor", {}).get("name"))
    cursor = page["next"] or cursor
    time.sleep(30)

Option C: stream events live

Keep one connection open to GET /stream and get events pushed as Server-Sent Events the moment they happen, with no public endpoint and no polling interval. Filter with types=monitor.*, and resume after a disconnect with Last-Event-ID or after. The server closes the stream about every 10 minutes, so reconnect in a loop. Details in Events.

curl -N "https://upbutler.com/api/v1/stream?types=monitor.*" \
  -H "Authorization: Bearer $UPBUTLER_KEY"

Option D: subscribe to someone else's status page

Depend on a third party that runs its status page on UpButler? Subscribe your webhook to it. This needs no API key. You'll receive incident.*, maintenance.* and component.status_changed for the components you care about. First we send a challenge, and your endpoint must echo it back. See Subscribers.

curl -X POST https://upbutler.com/api/v1/public/pages/fetchlayer/subscribers \
  -H "Content-Type: application/json" \
  -d '{
    "type": "webhook",
    "url": "https://my-agent.example.com/upbutler",
    "components": ["reddit"],
    "agent": "scraper-orchestrator"
  }'

6. Prefer MCP?

Every operation above is also an MCP tool (services_add, incidents_create, events_list, public_subscribe, …):

Claude Code
claude mcp add --transport http upbutler https://upbutler.com/mcp \
  --header "Authorization: Bearer ub_live_..."

Config for Cursor and generic clients, plus the full tool list, is in MCP server.

Good agent manners

  • Send Idempotency-Key on POSTs you might retry. The first response is replayed for 24 hours.
  • Read the hint in error responses. It tells you what to do next. See Errors.
  • Get your own key: POST /keys with agent: "deploy-bot", so the timeline shows who did what.
  • Don't poll public status faster than every 30 seconds. Subscribe instead.