Give your agent a pager and a status page.
UpButler is uptime monitoring your agent can run by itself. It can create a workspace, plug in services, prove its own jobs ran, tell customers what broke, and react when something it depends on goes down. You claim the workspace whenever you're ready.
- MCP tools
- 94
- Calls to go live
- 2
- Humans required
- 0
- Webhook signing
- Standard Webhooks
From zero to a public status page in two calls.
1
Bootstrap a workspace
No sign-up and no key. The response has an API key to use right away and a claim link for the human who will own the workspace.
curl -X POST https://upbutler.com/api/v1/agent/bootstrap \ -H "Content-Type: application/json" \ -d '{"agentName": "deploy-bot", "workspaceName": "Acme"}'{ "workspace": { "id": "ws_3kd9…", "name": "Acme", "plan": "free", "unclaimedUntil": "2026-10-16T09:12:00.000Z" }, "apiKey": "ub_live_8Hq2…", "claimUrl": "https://upbutler.com/claim/clm_Vx…", "next": [ "Store apiKey securely; …", "POST /api/v1/services …", "Share claimUrl …" ], "mcp": "https://upbutler.com/mcp" }2
Plug in a service
One call creates the monitor, the status page (from the slug) and the component. Pass
periodSecinstead ofurlto get a heartbeat URL for a job.curl https://upbutler.com/api/v1/services \ -H "Authorization: Bearer ub_live_8Hq2…" \ -H "Content-Type: application/json" \ -d '{"name": "Search API", "url": "https://api.acme.dev/health", "statusPage": "acme", "group": "APIs"}'3
Hand the claim link to your human
Monitoring, status pages, webhooks and AI reports work before anyone claims the workspace. Claiming makes a person the owner and unlocks email. Unclaimed workspaces are deleted after 7 days.
deploy-bot
I set up monitoring for the Search API and published it at upbutler.com/s/acme. To own the workspace and get email alerts, claim it here within 7 days:
https://upbutler.com/claim/clm_Vx…
Connect over MCP.
One streamable-HTTP endpoint, the same Bearer key as the REST API. Every operation is a typed tool with read-only and destructive hints, and the server tells your agent the common flows when it connects.
No key yet? Connect without one and call agent_bootstrap, then reconnect with the key it returns.
claude mcp add --transport http upbutler https://upbutler.com/mcp \
--header "Authorization: Bearer ub_live_..."// .cursor/mcp.json
{
"mcpServers": {
"upbutler": {
"url": "https://upbutler.com/mcp",
"headers": { "Authorization": "Bearer ub_live_..." }
}
}
}{
"mcpServers": {
"upbutler": {
"type": "http",
"url": "https://upbutler.com/mcp",
"headers": { "Authorization": "Bearer ub_live_..." }
}
}
}curl https://upbutler.com/mcp \
-H "Authorization: Bearer ub_live_..." \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "monitors_list", "arguments": {"state": "down"}}}'All 94 tools
Agents
- agent_bootstrap
- services_add
- heartbeat_ping
- components_push
- agent_whoami
Monitors
- monitors_list
- monitors_create
- monitors_get
- monitors_update
- monitors_delete
- monitors_check
- monitors_checks
- checks_get
- regions_list
- monitors_regions
Status pages
- pages_list
- pages_create
- pages_get
- pages_update
- pages_delete
- pages_verifyDomain
- pages_preview
- pages_groups
- pages_subscribers
- subscribers_delete
Components
- components_create
- components_update
- components_delete
- components_override
- pages_push
Incidents
- incidents_list
- incidents_get
- incidents_create
- incidents_update
- incidents_resolve
- incidents_analyze
- maintenance_create
- incidents_ack
- incidents_unack
- incidents_postmortem
- incidents_postmortem_get
- incidents_postmortem_save
- incidents_postmortem_publish
- templates_list
- templates_create
- templates_update
- templates_delete
- templates_render
Alerts
- channels_list
- channels_create
- channels_update
- channels_delete
- channels_test
Public status
- public_status
- public_incidents
- public_incident
- public_subscribe
- public_subscriber_get
- public_subscriber_update
- public_unsubscribe
- public_responseTimes
Workspace
- workspace_get
- workspace_update
- members_list
- keys_list
- keys_create
- keys_revoke
- audit_list
Events
- events_list
Billing
- plans_list
- billing_checkout
Import
- import_preview
- import_apply
- import_status
- import_jobs
On-call
- oncall_current
- oncall_schedules_list
- oncall_schedules_get
- oncall_schedules_create
- oncall_schedules_update
- oncall_schedules_delete
- oncall_overrides_create
- oncall_overrides_delete
- oncall_members
- escalation_list
- escalation_create
- escalation_update
- escalation_delete
Deploys
- deploys_create
- deploys_list
- deploys_delete
Digest
- digest_settings
- digest_settings_update
- digest_preview
Prompts to copy.
Paste these into Claude Code, Cursor or your own agent once UpButler is connected.
Use the UpButler MCP server to monitor this project. Find our public URLs and health endpoints, add each one with services_add to the status page "acme", grouped as APIs, Web or Jobs. For every cron job or worker, create a heartbeat (services_add with periodSec) and add the ping to the job. Show me the status page URL at the end.
After each deploy, watch UpButler for 10 minutes: poll events_list with after=<last id> for monitor.down or monitor.degraded on the services you changed. If one fires, open an incident with incidents_create (polish: true), roll back, and resolve it with incidents_resolve once monitors are up again.
Post an UpButler update on the open incident: "found the bad migration, rolled back, error rate dropping". Use incidents_update with status monitoring and polish: true so customers get a clear message.
Our payment provider publishes an UpButler status page with the slug "paycorp". Subscribe our endpoint https://ops.acme.dev/hooks/paycorp to it with public_subscribe (type webhook, components ["payments-api"]). When a component.status_changed event says it's degraded or down, switch checkout to the fallback provider.
React to downtime automatically.
Subscribe a webhook to any public UpButler status page, yours or a provider's, and your agent receives incident.*, maintenance.* and per-component component.status_changed events.
- Your endpoint proves it's yours by echoing a challenge once.
- Every delivery is signed with
webhook-id,webhook-timestampandwebhook-signature, so any Standard Webhooks library can verify it. - Failed deliveries retry after 30 s, 2 min, 10 min, 30 min, 1 h, 3 h and 6 h.
- Limit a subscription to the components you care about.
curl -X POST https://upbutler.com/api/v1/public/pages/paycorp/subscribers \
-H "Content-Type: application/json" \
-d '{"type": "webhook", "url": "https://ops.acme.dev/hooks/paycorp",
"components": ["payments-api"], "agent": "checkout-guard"}'import { createHmac, timingSafeEqual } from "node:crypto";
// whsec_... from the subscribe response (or the channel's signingSecret)
const secret = Buffer.from(process.env.UPBUTLER_SECRET!.replace(/^whsec_/, ""), "base64");
export async function POST(req: Request) {
const body = await req.text();
const event = JSON.parse(body);
// 1. Handshake: echo the challenge to activate the subscription.
if (event.type === "subscription.verify") {
return Response.json({ challenge: event.challenge });
}
// 2. Verify the Standard Webhooks signature on everything else.
const id = req.headers.get("webhook-id")!;
const ts = req.headers.get("webhook-timestamp")!;
const expected = "v1," + createHmac("sha256", secret).update(`${id}.${ts}.${body}`).digest("base64");
const valid = (req.headers.get("webhook-signature") ?? "").split(" ").some(
(s) => s.length === expected.length && timingSafeEqual(Buffer.from(s), Buffer.from(expected)),
);
if (!valid || Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
return new Response("invalid signature", { status: 401 });
}
// 3. React. webhook-id is stable across retries: use it to dedupe.
if (event.type === "component.status_changed" && event.data.component.key === "payments-api") {
if (event.data.to === "operational") await useProvider("paycorp");
else await useProvider("fallback");
}
return new Response("ok");
}Prove your agents are still working.
A long-running agent that silently stops is worse than one that crashes loudly. Give it a heartbeat: if no ping arrives within the period plus grace time, the monitor goes down and you're alerted. A ping to /fail alerts immediately.
Heartbeat URLs need no API key; the token is the secret. GET or POST, with an optional message.
import requests, time
HB = "https://upbutler.com/hb/hb_7Qx2kd" # services_add {"name": "Triage agent", "periodSec": 600}
while True:
try:
triaged = run_triage_cycle()
requests.post(HB, json={"message": f"triaged {triaged} tickets"}, timeout=10)
except Exception as e:
requests.post(f"{HB}/fail", json={"message": str(e)[:400]}, timeout=10)
time.sleep(300)# .github/workflows/nightly-export.yml
- name: Export
run: ./scripts/export.sh
- name: Report to UpButler
if: always()
run: |
if [ "${{ job.status }}" = "success" ]; then
curl -fsS "https://upbutler.com/hb/${{ secrets.UPBUTLER_HB }}?msg=export+ok"
else
curl -fsS "https://upbutler.com/hb/${{ secrets.UPBUTLER_HB }}/fail"
fi# API form: report status, message and how long the job took
curl -X POST https://upbutler.com/api/v1/heartbeat/hb_7Qx2kd \
-H "Content-Type: application/json" \
-d '{"status": "up", "message": "indexed 18,204 docs", "durationMs": 41250}'Tell it what you shipped.
Record a deploy marker from CI or from the agent that ran the release. When something breaks soon after, the AI analysis connects the two: "errors began 2 minutes after deploy 1.4.2". Deploys never show up in public text.
Scope a deploy to a service, specific monitors or a status page, or leave it workspace-wide.
Deploy markers- uses: upbutler/action@v1
with:
api-key: ${{ secrets.UPBUTLER_API_KEY }}
version: ${{ github.ref_name }}
environment: production
service: apicurl -X POST https://upbutler.com/api/v1/deploys \
-H "Authorization: Bearer $UPBUTLER_KEY" \
-H "Content-Type: application/json" \
-d '{"version": "1.4.2", "commit": "abc1234def", "environment": "production", "service": "api"}'{ "name": "deploys_create",
"arguments": { "version": "1.4.2", "service": "api" } }Agent questions.
Does my agent need an account to start?
No. agent_bootstrap (or POST /api/v1/agent/bootstrap) creates a free workspace and returns an API key immediately. It is rate-limited to 5 workspaces per hour per IP address.
What happens if nobody claims the workspace?
Unclaimed workspaces are deleted after 7 days. Claiming takes one click on the claim link and a sign-in by email; after that a person owns it and email alerts and subscribers are unlocked.
Which tools work without an API key?
Bootstrap, heartbeats, component pushes by token, plan listing and the public status tools (public_status, public_incidents, public_subscribe and friends). Everything that reads or changes a workspace needs a key.
How are agent actions shown to humans?
Create a key per agent with keys_create and an agent name. Incident updates, monitors and subscriptions are attributed to that agent in the dashboard and timelines.
Should I use webhooks, the live stream or polling?
Webhooks if your agent has a public HTTPS endpoint: they are signed, retried with backoff for about 11 hours, and unmetered. If it can hold a connection open, stream GET /api/v1/stream (Server-Sent Events) and resume with Last-Event-ID. Otherwise poll events_list with after set to the last event id you saw.
Hand your agent the keys.
Read the agent quickstart, or sign in and create a key for it yourself.