Pulse Documentation

Setup, App, CLI, and API

Deploy the app, drive it from pulsectl, and extend with your agents.

1.0   Introduction

What Pulse is

Pulse is a self-hosted uptime monitor. It checks your public HTTP endpoints every minute, confirms failures before alerting, records every incident with its full state history, and publishes a public status page.

Everything is driven through one control plane: the dashboard, the pulsectl CLI, and the HTTP API all speak the same versioned contract with the same permissions model. A human clicking "Pause" and an agent calling POST /api/v1/monitors/api/pause perform the identical operation.

  • Next.js 16 app (App Router, React 19) deployed to Vercel
  • Postgres on Neon via Drizzle ORM for durable history
  • Vercel Edge Config for accepted configuration and low-latency public state
  • Resend for outage and recovery email
  • Go CLI (pulsectl) for terminals, scripts, and agents
  • MIT licensed, small enough to understand end to end

Architecture

Pulse is serverless end to end. There is no long-running process to babysit.

  • Vercel Cron invokes /api/cron/check-monitors every minute. The scheduler packs due monitors into one batch, checks them concurrently, and resolves state transitions atomically.
  • Neon stores incidents, exception payloads, and compact rollups. An adaptive storage governor keeps the database inside a 500 MB budget by compacting routine checks while preserving incident detail.
  • Edge Config serves the accepted monitoring configuration and the latest public state with minimal latency, and acts as a fallback if the database is unreachable.
  • Resend sends one email when a monitor is confirmed down and one when it recovers.
  • A second cron, /api/cron/maintenance, runs daily at 03:17. It compacts routine checks, builds rollups, and snapshots database usage.

Monitor states

A monitor moves through UPVERIFYING_DOWNDOWNVERIFYING_UPUP. Failures are confirmed over consecutive checks (the failure threshold) before an incident opens, and recoveries are confirmed the same way before it resolves. Monitors can also be PENDING, PAUSED, or ARCHIVED.

2.0   Automated setup

Deploy with an agent

Point a coding agent at the prompt below and it discovers your active projects, provisions Vercel, Neon, Edge Config, and Resend, deploys Pulse, and configures monitoring end to end. Run it directly with the codex tab, or copy the plain prompt for Claude Code, OpenClaw, Hermes, or any other agent runner.

codex \
  -m gpt-5.6-sol \
  -c model_reasoning_effort='"low"' \
  --sandbox danger-full-access \
  --ask-for-approval on-request \
  "$(cat <<'PROMPT'
Act as the deployment owner for Pulse, the self-hosted uptime monitor at https://github.com/0xSMW/pulse-uptime: inspect the repository, documentation, `.env.example`, migrations, deployment configuration, CLI manifest, and OpenAPI spec; identify every active public web project the user owns or maintains by inspecting available workspaces, repositories, deployment providers, project metadata, configured domains, production environments, and existing infrastructure, verify each endpoint is genuinely active and production-facing, then deploy Pulse and configure monitoring for all of them without waiting for the user to enumerate URLs. Establish the correct Vercel team and project, create and configure Neon Postgres, Vercel Edge Config, and Resend, verify the sending domain, generate independent high-entropy application secrets, set the one-time bootstrap token, apply migrations through the direct connection, run the full verification suite, and deploy production. Determine the user’s commonly used work email from authoritative account metadata, repository identities, deployment-provider profiles, domain ownership, and existing project configuration; use the strongest consistent match to claim the first administrator account, asking only when multiple plausible addresses remain unresolved. Generate a unique password with at least 24 random characters, store the email, password, Pulse URL, creation date, and recovery notes in the user’s available password manager when supported; otherwise create a clearly named local credentials file outside the repository with owner-only permissions, never print its contents to chat, logs, shell history, or command output, report its exact path, and instruct the user to import it into their password manager and securely delete the file afterward. Remove the bootstrap token and migration-owner credential from the runtime environment after account creation. Build a complete grouped configuration covering every discovered active web project and its meaningful health endpoints, using appropriate intervals, timeouts, expected status ranges, failure thresholds, recovery thresholds, and recipients; validate, plan, review, and apply it through `pulsectl`, automatically applying additive and non-destructive changes while requiring explicit user approval only for destructive operations. Run a live test for every monitor, investigate and correct failed checks where access permits, verify `/api/v1/version`, `/openapi/v1.json`, `/status`, cron authentication, database health, monitoring activation, and end-to-end outage, recovery, and test-email delivery. Use least-privilege credentials, preserve existing production infrastructure, keep all secrets out of source control and agent transcripts, and pause only when an external login, billing consent, domain verification, secret entry, unresolved administrator-email ambiguity, or destructive-change approval requires the user. Finish with the Pulse URL, public status-page URL, administrator email used, secure credential-storage location, every project and endpoint monitored, resources created, configuration applied, live-test results, verification evidence, unresolved blockers, and exact commands for future administration.
PROMPT
)"

Full access, minimal gating

This grants filesystem and network access and only pauses for logins, billing consent, secrets, and destructive changes. Run it against infrastructure you own, and read the plan before it applies anything.

3.0   Getting started

Deploy to Vercel

One Next.js project, one Neon database, one Edge Config, and a Resend key. About fifteen minutes from clone to first check.

1

Create the Vercel project

Link the repository to the Vercel team that will own the deployment, then run the inspect command to confirm the project landed in that team before creating any resources.

bash
vercel link --yes --project pulse-uptime --scope <team>
vercel project inspect pulse-uptime --scope <team>
2

Create the Neon database

Use the pooled connection string as DATABASE_URL and the direct one as DATABASE_URL_UNPOOLED, then apply migrations over the direct connection.

bash
neonctl projects create --name pulse-uptime
neonctl connection-string --project-id <project-id> --pooled
neonctl connection-string --project-id <project-id>

# apply migrations with the direct URL in .env.local
set -a; source .env.local; set +a
pnpm db:migrate
3

Create the Edge Config

Create an Edge Config named pulse-uptime in the same team and seed the single monitoring key with a valid empty configuration.

json
{
  "schemaVersion": 1,
  "configVersion": 0,
  "settings": {
    "concurrency": 25,
    "defaultTimeoutMs": 8000,
    "defaultFailureThreshold": 2,
    "defaultRecoveryThreshold": 2,
    "defaultRecipients": [],
    "userAgent": "Pulse/1.0"
  },
  "monitors": []
}
4

Configure Resend

Verify the sending domain, create a sending-only API key, and set RESEND_API_KEY and RESEND_FROM_EMAIL. You can finish setup without email. The readiness screen flags missing email keys as a warning, not a blocker, and alerts stay unverified until a test message is delivered (pulsectl notification test).

5

Connect Porkbun

Set PORKBUN_API_KEY and PORKBUN_SECRET_KEY as server-only variables. In Settings → System → Domain monitoring, check the connection and enable the signed webhook. Pulse polls registration facts and receives domain.renewed and domain.expiring events at /api/webhooks/porkbun. Porkbun never manages SSL.

6

Set application secrets

Generate three independent random values of at least 32 bytes.

bash
# one value per secret, never reuse
openssl rand -base64 32   # CRON_SECRET
openssl rand -base64 32   # API_TOKEN_HASH_KEY
openssl rand -base64 32   # DEVICE_AUTH_SECRET
7

Deploy and verify

bash
pnpm verify
vercel deploy --prod --scope <team>

After deploying, confirm /api/v1/version, /openapi/v1.json, and /status respond, then complete first-run onboarding at your deployment URL: create the administrator account, verify readiness, and activate monitoring.

Environment variables

The canonical list lives in .env.example. Values are validated at boot.

Variable Required Purpose
DATABASE_URL Yes Pooled Neon Postgres connection string used by the app.
DATABASE_URL_UNPOOLED Yes Direct connection string, required for migrations.
EDGE_CONFIG Yes Edge Config read connection string.
EDGE_CONFIG_ID Yes Edge Config to write accepted configuration to.
VERCEL_API_TOKEN Yes Token authorized to update the Edge Config.
VERCEL_PROJECT_ID No Present in .env.example but not currently read by the app.
VERCEL_TEAM_ID No Team that owns the Edge Config. Set it when the Edge Config is team-owned.
RESEND_API_KEY Recommended Resend key (re_…) for alert email.
RESEND_FROM_EMAIL Recommended Verified sender, e.g. Pulse <alerts@superposition.app>.
CRON_SECRET Yes Authenticates Vercel Cron requests. ≥ 32 characters.
API_TOKEN_HASH_KEY Yes Keyed hashing for API token digests and purpose-derived encryption for protected provider data. ≥ 32 characters.
PORKBUN_API_KEY No Server-only Porkbun API credential for domain facts.
PORKBUN_SECRET_KEY No Server-only Porkbun secret for domain facts.
DEVICE_AUTH_SECRET Yes Signs the CLI device-authorization flow. ≥ 32 characters.
NEXT_PUBLIC_APP_URL Yes Canonical deployment URL.
NEXT_PUBLIC_STATUS_PAGE_NAME No Public status page title. Defaults to “Pulse Status”.

Email alerts

Pulse sends exactly three kinds of email, all from RESEND_FROM_EMAIL. When the failure threshold confirms an outage, every recipient on the monitor gets one message. When the recovery threshold confirms the fix, they get one more. Delivery runs through a durable outbox, so retries never duplicate a message.

The subject line is the headline: a monitor named Inference pages you as “Inference is down”. Both incident emails deep-link to /incidents/:id, and that is the whole surface. There are no digests, reminders, or marketing follow-ups.

Hardening your deployment

Default secure, but production needs a few extra operator-side controls tightened.

Run under a least-privilege database role. Create a dedicated Postgres login with only the grants the app needs, and point the pooled DATABASE_URL at it. Keep the Neon owner (neondb_owner) and the unpooled DATABASE_URL_UNPOOLED credential for migrations only, and remove it from the application environment so the running app never authenticates as the schema owner.

Add the browser security headers. Production ships HSTS. Add the rest of the central policy so the dashboard is defended in depth.

Header Value
Content-Security-Policy Restrict script/style/connect sources and set frame-ancestors.
X-Frame-Options DENY — legacy clickjacking defense alongside frame-ancestors.
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy Deny the browser features the app does not use.
Strict-Transport-Security Already shipped in production.

Claim the first admin immediately. A fresh deployment boots into onboarding with no administrator. Claim the initial account the moment the deploy is live so no one else can. Creating that first admin requires a one-time bootstrap credential supplied through the PULSE_BOOTSTRAP_TOKEN environment variable; set it before first run, use it to create the admin, then remove it.

Rotate provider secrets and scope them tightly. Generate fresh values for RESEND_API_KEY, CRON_SECRET, DEVICE_AUTH_SECRET, and API_TOKEN_HASH_KEY for your own deployment rather than reusing any shared value, and scope the Resend key to sending only.

Install the CLI only from the official source. Until signed release artifacts exist, build pulsectl from source with Go and nothing else.

bash
go install github.com/0xSMW/pulse-uptime/cli/cmd/pulsectl@latest

4.0   Application

The dashboard, screen by screen

Six screens, no configuration sprawl. Everything the app can do is also available from the CLI and API.

Onboarding · /onboarding

A fresh deployment boots straight into onboarding. A readiness gate checks Vercel, the database, Edge Config, and email before anything else, then three steps create the administrator account, add a first monitor, and verify a live check before activating it. A closing screen hands off to the CLI and agent tokens. Sign in afterwards at /login.

pulse.superposition.app/onboarding
System check Make sure Pulse is ready Verify required services before creating your account
System readiness
Vercel
Database
Edge Config
Email
Check Again Continue Without Alerts

Overview · /

The home screen lists every monitor with its live state, 24-hour uptime, check timeline, and latency. A health banner summarizes the whole system, and the ⌘K command palette jumps anywhere or runs quick actions. New Monitor opens a sheet that asks for four fields: name, URL, group, and interval. Method, timeout, expected status, and thresholds are tucked behind Advanced.

pulse.superposition.app/
New Monitor
Monitor Uptime 24h Latency Last checked

Monitor detail · /monitors/:id

One monitor's full story: the current state with its confirmation progress, a segmented check timeline, a latency chart, and recent transitions. Pause, resume, edit, test, and archive actions live here. Archiving keeps history. Re-create a monitor with the same id, through the API or config apply, and it comes back.

pulse.superposition.app/monitors/api
GET
Latest latency
Uptime 24h
Uptime 7d
Uptime 30d
Availability
24h 7d 30d 90d
Response time
24h 7d 30d
Recent incidents
StartedDurationCause
Recent checks
TimeResultLatency
Configuration Edit

Incidents · /incidents

Every failure gets a durable record. The list filters by monitor and resolution state. The detail view (/incidents/:id) shows the cause, the event trail of every state transition with timestamps, and which notifications were delivered. Incidents open only after the failure threshold confirms an outage, so flapping checks don't page you.

pulse.superposition.app/incidents
All Ongoing Resolved
Ongoing
Verifying up
History
MonitorStateStartedDurationNotifications
Resolved
Resolved
Resolved

Settings · /settings

Four areas, one per sidebar item:

  • General: theme, time zone, default recipients, and outbound sender info.
  • Monitors: every monitor, plus the groups that structure the dashboard and the public status page.
  • Access: create scoped API tokens for agents and automation, review linked CLI installations, and revoke either.
  • System: live storage budget, per-category usage, how long each data category is kept, and the governor's current mode. Domain monitoring shows Porkbun connection, webhook, polling, and expiry-alert status.
pulse.superposition.app/settings/system
Back to app
General
Monitors
Access
System
Storage Healthy
Projected
Available
Data breakdown

Domain monitoring · /settings/system

Porkbun supplies registration expiry and auto-renew for covered domains. The System card runs every ten minutes and reads the Porkbun portfolio for due domains, normally once per 24 hours. It accepts signed domain.renewed and domain.expiring webhooks. Expiry alerts are off by default and notify default recipients at 30 and 14 days. Certificate checks remain direct TLS probes. Certificate alerts are out of scope.

Status page · /status

A public, indexable status page that revalidates every 30 seconds, with per-group views at /status/:group. It shows current operational state per monitor without exposing internal detail. The page title comes from NEXT_PUBLIC_STATUS_PAGE_NAME.

pulse.superposition.app/status
All systems operational
Production
Recent incidents

5.0   CLI

pulsectl

One Go binary for monitors, incidents, status, tokens, and complete configuration. Humans link through the browser and agents use scoped tokens.

Install & link

bash
go install github.com/0xSMW/pulse-uptime/cli/cmd/pulsectl@latest
pulsectl me --server https://pulse.superposition.app

Build the CLI from source with Go until signed release artifacts are published. Replace the example URL with your deployment. pulsectl me triggers an interactive browser approval on first run and stores the session in your operating-system keyring. Once linked, it reports who you are and which scopes you hold.

Authentication

Interactive linking uses an OAuth-style device flow:

  1. The CLI requests a device authorization and prints a short user code.
  2. Your browser opens /cli/authorize on the server, where you confirm the code as a signed-in administrator.
  3. The CLI polls until approval, then stores the session token in the OS keyring, never on disk.
bash
pulsectl auth login          # link this installation
pulsectl auth status         # inspect the current session
pulsectl auth unlink --yes   # remove it

# headless environments: pass a scoped token instead
export PULSECTL_TOKEN=pulse_live_...
echo "$PULSECTL_TOKEN" | pulsectl monitor list --token-stdin

No token flag, on purpose

pulsectl accepts tokens only through the environment or stdin, so secrets stay out of shell history and process listings.

Contexts & flags

Contexts are named server profiles stored in local config, so one binary can drive several Pulse deployments.

bash
pulsectl context add production --url https://pulse.superposition.app --use
pulsectl context add staging --url https://staging.superposition.app
pulsectl context list
pulsectl monitor list --context staging

Settings resolve in precedence order: flags, then environment, then the active context.

Flag Env Purpose
--context PULSECTL_CONTEXT Named context to use for this invocation.
--server PULSECTL_URL Server URL, overriding the context.
-o, --output PULSECTL_OUTPUT Output format: table, json, jsonl, yaml, tsv.
--timeout PULSECTL_TIMEOUT Per-request timeout.
--token-stdin PULSECTL_TOKEN Read a bearer token from stdin / the environment.
--no-color NO_COLOR Disable ANSI color.
--debug None Verbose request diagnostics on stderr.

Command reference

Every command declares the API scope it needs, the same scopes you grant to tokens. authenticated means any signed-in principal.

Command Purpose Scope
me Show the current principal or link this installation. authenticated
auth login | status | unlink Link, inspect, or unlink an installation and revoke its credentials. authenticated
context add | list | show | use | remove Manage named server profiles (local only). None
status Public service status snapshot. status:read
monitor list | get | watch Read monitors. watch streams state changes. monitors:read
monitor create | update Create or edit a monitor with flags mirroring the API fields. monitors:write
monitor pause | resume | archive Lifecycle operations. archive preserves history and asks for --yes. monitors:write
monitor test Run one live check now. Exits 4 if the target fails. monitors:write
group list List groups with monitor counts. monitors:read
group create | rename | delete Manage groups. Only empty groups can be deleted. monitors:write
incident list | get Read incident history and detail. incidents:read
config export | schema Export accepted configuration / print its JSON schema. config:read
config validate | plan | apply The declarative workflow. See §5. config:write
token create | list | revoke Manage scoped API tokens. tokens:manage
notification test Send a test email to confirm delivery. notifications:test
version CLI version plus server compatibility check. None
doctor Diagnose config, context, connectivity, and auth. None
completion | help Shell completions. help --output json emits a machine-readable manifest. None

Output formats

Data goes to stdout, diagnostics to stderr. On a TTY the default is a table. When piped, output defaults to json (jsonl for monitor watch), so scripts never parse table art.

bash
pulsectl monitor list -o json | jq '.[].id'
pulsectl monitor watch -o jsonl --state DOWN
pulsectl incident list -o yaml

Exit codes

Exit codes are typed so scripts and agents can branch without parsing messages.

Code Meaning
0 Success.
1 Unexpected error.
2 Invalid input: a bad flag, malformed value, or unusable file.
3 Authentication required or the session is no longer valid.
4 Operational failure, such as monitor test against a failing target.
5 Not found.
6 Conflict: the resource changed underneath you (stale ETag, duplicate id).
7 Permission denied: the principal lacks the required scope.
8 Rate limited. Retry after the server-provided delay.
9 Server unavailable or unreachable.
10 Partial success.
130 Interrupted with Ctrl-C.

Agents

Agents get the same control plane as people. Create a scoped token in Settings → Access and export three variables. The CLI then runs without a browser, and help --output json hands your agent a manifest of every command, flag, and required scope.

bash
export PULSECTL_URL=https://pulse.superposition.app
export PULSECTL_TOKEN=pulse_live_...
export PULSECTL_OUTPUT=json

pulsectl help --output json   # machine-readable command manifest
pulsectl config schema        # JSON schema for the config document
pulsectl monitor list
Use pulsectl --help to discover commands. Authenticate through PULSECTL_TOKEN, prefer --output json, and never print or persist the token.

6.0   Configuration

Config as code

The entire monitoring setup (settings, groups, and monitors) is one declarative document you can export, diff, review, and apply. Terraform style, with conflict detection and destructive-change approval built in.

The config file

yaml
version: 2
settings:
  concurrency: 25
  defaultTimeoutMs: 8000
  defaultFailureThreshold: 2
  defaultRecoveryThreshold: 2
  defaultRecipients:
    - codex@superposition.app
  userAgent: Pulse/1.0
groups:
  - id: production
    name: Production
monitors:
  - id: api
    name: API
    url: https://inference.superposition.app/health
    enabled: true
    groupId: production
    method: GET
    intervalMinutes: 1
    timeoutMs: 8000
    expectedStatus: { minimum: 200, maximum: 299 }
    failureThreshold: 2
    recoveryThreshold: 2
    recipients: []
Field Constraints
id Kebab-case, 3-64 chars (^[a-z0-9]+(-[a-z0-9]+)*$). Stable, since it names the monitor in the API and CLI.
name 1-80 characters.
url Public http(s) URL without embedded credentials. Private and reserved addresses are rejected.
method GET or HEAD.
intervalMinutes 1, 5, 10, or 15.
timeoutMs 1000-15000.
expectedStatus Inclusive range, each bound 100-599, maximum ≥ minimum.
failureThreshold 1-5 consecutive failed checks before an incident opens.
recoveryThreshold 1-5 consecutive passing checks before it resolves.
recipients Up to 20 unique email addresses.
groups Up to 100, each { id, name }. Monitors reference groupId.

Monitor ids must be unique, at most 100 monitors may be enabled, and the document may not exceed 55 KB. Exporting a v1 document automatically upgrades it to the v2 group format.

Plan & apply

bash
pulsectl config export --file monitors.yaml
# edit monitors.yaml
pulsectl config validate --file monitors.yaml
pulsectl config plan --file monitors.yaml
pulsectl config apply --file monitors.yaml --wait

validate checks the document against the schema and semantic rules without touching the server state. plan computes an exact diff plus content hashes of the base and target. The diff covers settings changes. For groups it lists creates, renames, and deletes. For monitors it lists creates, field-level updates, pauses, resumes, and archives. apply submits the plan and, with --wait, polls the resulting operation until it is accepted (written to Edge Config) or rejected.

Safety rails

  • Optimistic concurrency. Every export carries an ETag. Apply sends it back as If-Match along with the base, target, and plan hashes. If anyone changed configuration in between, the apply fails with a conflict instead of silently clobbering.
  • Destructive-change tripwire. A plan that archives monitors or deletes groups sets destructiveConsentRequired. Non-interactive applies must state intent twice: --allow-destructive --yes.
  • Archives, not deletes. Removing a monitor archives it with its history. Re-adding the same id restores it.

Review plans like code

Treat config plan output as a pull-request diff for your monitoring. Agents should run plan and show the diff to a human before applying.

7.0   API reference

One versioned API

Everything the dashboard and CLI do goes through /api/v1. The full machine-readable spec is served by your deployment at /openapi/v1.json.

Conventions

The base URL is your deployment. Pulse is self-hosted, so there is no shared api.pulse.dev. Every response is a typed envelope:

json
{
  "apiVersion": "v1",
  "kind": "MonitorList",
  "data": [ ],
  "meta": {
    "requestId": "req_01j8zqf7",
    "nextCursor": null
  }
}
  • Kinds. kind names the payload shape: Monitor, MonitorList, Incident, ConfigurationPlan, Error, and so on.
  • Pagination. List endpoints take limit and cursor. Follow meta.nextCursor until it is null.
  • Idempotency. Every mutating request must include an Idempotency-Key header containing a UUID. Retries with the same key return the original result.
  • Request ids. Every response carries meta.requestId for correlation.

Authentication & scopes

bash
curl https://pulse.superposition.app/api/v1/monitors \
  -H "Authorization: Bearer $PULSE_TOKEN"

Three principal types share one permission model:

  • Humans: first-party cookie sessions from the web app. They hold every scope, and mutations are origin-checked against the deployment URL as CSRF protection.
  • API tokens: created in Settings or via pulsectl token create. They carry exactly the scopes you grant, require an explicit expiry of at most 365 days (the CLI defaults to 90d), and are stored only as keyed digests.
  • CLI sessions: pulsectl installations linked through the browser device flow. Approval grants the full administrator scope set.
monitors:read monitors:write incidents:read config:read config:write notifications:test tokens:manage status:read

Requests without credentials get 401 with a WWW-Authenticate header. Authenticated requests missing a scope get 403 with error code SCOPE_DENIED.

Errors & rate limits

json
{
  "apiVersion": "v1",
  "kind": "Error",
  "error": {
    "code": "SCOPE_DENIED",
    "message": "This operation requires monitors:write.",
    "details": {},
    "requestId": "req_01j8zqf7"
  }
}

Rate limits are per principal over a five-minute window: 600 reads and 120 mutations. Exceeding either returns 429 with a Retry-After header. Validation failures return 400 with field-level detail in error.details. Concurrency conflicts (stale If-Match, duplicate ids, reused idempotency keys with different bodies) return 409.

Version & principal

GET/api/v1/version

Supported API versions plus the minimum and latest compatible CLI versions. Unauthenticated, used by pulsectl for compatibility checks and by deploy verification.

json
{
  "apiVersion": "v1",
  "kind": "Version",
  "data": {
    "supportedApiVersions": ["v1"],
    "minimumCliVersion": "0.3.0",
    "latestCliVersion": "0.3.2"
  },
  "meta": { "requestId": "req_01j8zqf7" }
}

GET/api/v1/me

Who is calling: principal type, email or token identity, granted scopes, and the linked CLI installation when the caller is a device session.

authenticated
json
{
  "apiVersion": "v1",
  "kind": "Me",
  "data": {
    "principalType": "api_token",
    "email": null,
    "tokenId": "0d9f6c1e-…",
    "tokenName": "ci-agent",
    "scopes": ["monitors:read", "incidents:read"],
    "installation": null
  },
  "meta": { "requestId": "req_01j8zqf7" }
}

Monitors

GET/api/v1/monitors

List monitors, sorted by state by default so problems surface first.

monitors:read
Query Description
state UP, DOWN, VERIFYING_UP, VERIFYING_DOWN, PENDING, PAUSED, ARCHIVED.
group, groupId Filter by group name or id.
enabled true or false.
sort state (default), name, or id.
limit, cursor Pagination.
bash
curl "https://pulse.superposition.app/api/v1/monitors?state=DOWN" \
  -H "Authorization: Bearer $PULSE_TOKEN"

# equivalent
pulsectl monitor list --state DOWN -o json
json
{
  "apiVersion": "v1",
  "kind": "MonitorList",
  "data": [
    {
      "id": "api",
      "name": "API",
      "url": "https://inference.superposition.app/health",
      "enabled": true,
      "group": "Production",
      "groupId": "production",
      "method": "GET",
      "intervalMinutes": 1,
      "timeoutMs": 8000,
      "expectedStatus": { "minimum": 200, "maximum": 299 },
      "failureThreshold": 2,
      "recoveryThreshold": 2,
      "recipients": ["codex@superposition.app"],
      "state": "DOWN"
    }
  ],
  "meta": { "requestId": "req_01j8zqf7", "nextCursor": null }
}

POST/api/v1/monitors

Create a monitor. id, name, and url are required. Everything else falls back to the global defaults. Field constraints are the same as the config file.

monitors:write
bash
curl -X POST https://pulse.superposition.app/api/v1/monitors \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id": "console", "name": "Console", "url": "https://console.superposition.app"}'

# equivalent
pulsectl monitor create --id console --name "Console" --url https://console.superposition.app

GET/api/v1/monitors/ { monitorId}

Fetch one monitor, including its live state and timestamps.

monitors:read
bash
curl https://pulse.superposition.app/api/v1/monitors/api \
  -H "Authorization: Bearer $PULSE_TOKEN"

PATCH/api/v1/monitors/ { monitorId}

Partial update. Send only the fields you are changing. Group assignment uses groupId (or null to clear).

monitors:write
bash
curl -X PATCH https://pulse.superposition.app/api/v1/monitors/api \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"intervalMinutes": 5, "timeoutMs": 10000}'

DELETE/api/v1/monitors/ { monitorId}

Archive the monitor. History is preserved. Re-creating the same id restores it.

monitors:write
bash
curl -X DELETE https://pulse.superposition.app/api/v1/monitors/api \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

POST/api/v1/monitors/ { monitorId}/pause

Stop checking without losing state. The companion POST …/resume re-enables checks.

monitors:write
bash
curl -X POST https://pulse.superposition.app/api/v1/monitors/api/pause \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
curl -X POST https://pulse.superposition.app/api/v1/monitors/api/resume \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

POST/api/v1/monitors/ { monitorId}/test

Run one live check immediately without affecting monitor state, ideal for verifying a fix before resuming.

monitors:write
json
{
  "apiVersion": "v1",
  "kind": "MonitorTest",
  "data": {
    "successful": true,
    "method": "GET",
    "finalUrl": "https://inference.superposition.app/health",
    "statusCode": 200,
    "latencyMs": 42,
    "redirectCount": 0,
    "errorCode": null,
    "errorMessage": null
  },
  "meta": { "requestId": "req_01j8zqf7" }
}

Groups

GET/api/v1/groups

List groups with their monitor counts.

monitors:read
json
{
  "apiVersion": "v1",
  "kind": "GroupList",
  "data": [
    { "id": "production", "name": "Production", "monitorCount": 4 }
  ],
  "meta": { "requestId": "req_01j8zqf7", "nextCursor": null }
}

POST/api/v1/groups

Create a group with a stable kebab-case id and a display name.

monitors:write
bash
curl -X POST https://pulse.superposition.app/api/v1/groups \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id": "marketing", "name": "Marketing"}'

PATCH/api/v1/groups/ { groupId}

Rename a group. The id is permanent. Only name changes.

monitors:write
bash
curl -X PATCH https://pulse.superposition.app/api/v1/groups/marketing \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name": "Marketing Site"}'

DELETE/api/v1/groups/ { groupId}

Delete an empty group. Groups still containing monitors return a conflict.

monitors:write
bash
curl -X DELETE https://pulse.superposition.app/api/v1/groups/marketing \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

Incidents

GET/api/v1/incidents

Incident history, newest first, with limit/cursor pagination.

incidents:read
json
{
  "apiVersion": "v1",
  "kind": "IncidentList",
  "data": [
    {
      "id": "8f4e2c10-…",
      "monitorId": "api",
      "openedAt": "2026-07-12T08:14:00Z",
      "resolvedAt": "2026-07-12T08:31:00Z"
    }
  ],
  "meta": { "requestId": "req_01j8zqf7", "nextCursor": null }
}

GET/api/v1/incidents/ { incidentId}

Full incident detail: cause, duration, and the event trail of every state transition and notification.

incidents:read
bash
curl https://pulse.superposition.app/api/v1/incidents/8f4e2c10-… \
  -H "Authorization: Bearer $PULSE_TOKEN"

# equivalent
pulsectl incident get 8f4e2c10-…

Status

GET/api/v1/status

The service status snapshot that also powers the public status page: overall state plus per-monitor operational status.

status:read
bash
curl https://pulse.superposition.app/api/v1/status \
  -H "Authorization: Bearer $PULSE_TOKEN"

# equivalent
pulsectl status

Configuration

The API surface behind plan & apply. Exports carry an ETag. Applies send it back as If-Match.

GET/api/v1/config

Export the accepted configuration document with its ETag and content hash.

config:read
bash
curl -i https://pulse.superposition.app/api/v1/config \
  -H "Authorization: Bearer $PULSE_TOKEN"
# ETag: "sha256:2c26b46b…"

GET/api/v1/config/schema

The JSON Schema for the configuration document, so agents can validate locally before submitting.

config:read
bash
pulsectl config schema -o json

POST/api/v1/config/validate

Validate a candidate document against schema and semantic rules. Never mutates state.

config:write
json
{
  "apiVersion": "v1",
  "kind": "ConfigurationValidation",
  "data": { "valid": true, "errors": [] },
  "meta": { "requestId": "req_01j8zqf7" }
}

POST/api/v1/config/plan

Diff a candidate document against the accepted configuration. Returns base/target/plan hashes, the categorized diff, and destructiveConsentRequired.

config:write
json
{
  "apiVersion": "v1",
  "kind": "ConfigurationPlan",
  "data": {
    "baseConfigHash": "sha256:2c26…",
    "targetConfigHash": "sha256:9d1a…",
    "planHash": "sha256:77b0…",
    "diff": {
      "settingsChanged": [ ],
      "groupCreates": [ ],
      "groupUpdates": [ ],
      "groupDeletes": [ ],
      "creates": [ ],
      "updates": [ ],
      "pauses": [ ],
      "resumes": [ ],
      "archives": [ ],
      "unchanged": [ ]
    },
    "destructiveConsentRequired": false
  },
  "meta": { "requestId": "req_01j8zqf7" }
}

POST/api/v1/config/apply

Apply a previously planned change. Requires If-Match with the export ETag and the three hashes from the plan. Destructive plans also require allowDestructiveChanges: true. Returns an operation to poll.

config:write
bash
curl -X POST https://pulse.superposition.app/api/v1/config/apply \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'If-Match: "sha256:2c26…"' \
  -d '{
    "baseConfigHash": "sha256:2c26…",
    "targetConfigHash": "sha256:9d1a…",
    "planHash": "sha256:77b0…",
    "targetConfig": { … },
    "allowDestructiveChanges": false
  }'

GET/api/v1/config/operations/ { operationId}

Poll an apply operation. States: writtenaccepted once Edge Config confirms, or rejected / failed.

config:read
json
{
  "apiVersion": "v1",
  "kind": "ConfigurationOperation",
  "data": { "id": "b3a9e0d2-…", "state": "accepted" },
  "meta": { "requestId": "req_01j8zqf7" }
}

Tokens

POST/api/v1/tokens

Create a scoped token. The secret appears once in the response and is stored only as a keyed digest. Copy it immediately.

tokens:manage
bash
pulsectl token create --name ci-agent \
  --scope monitors:read --scope incidents:read \
  --expires-in 90d
json
{
  "apiVersion": "v1",
  "kind": "CreatedToken",
  "data": {
    "id": "0d9f6c1e-…",
    "name": "ci-agent",
    "token": "pulse_live_…",
    "scopes": ["monitors:read", "incidents:read"],
    "expiresAt": "2026-10-16T00:00:00Z"
  },
  "meta": { "requestId": "req_01j8zqf7" }
}

GET/api/v1/tokens

List tokens with scopes, expiry, revocation, and last-used timestamps. Secrets are never returned again.

tokens:manage
bash
pulsectl token list --all

DELETE/api/v1/tokens/ { tokenId}

Revoke a token immediately. Revocation is permanent.

tokens:manage
bash
pulsectl token revoke 0d9f6c1e-… --yes

Notifications

POST/api/v1/notifications/test

Send a test email through the configured Resend sender to confirm delivery end to end.

notifications:test
bash
pulsectl notification test --recipient codex@superposition.app

CLI authorization

The device flow behind pulsectl auth login. You rarely call these directly, but they're documented for custom clients.

POST/api/v1/cli-auth/device

Start a device authorization for a named installation. Returns the user code and verification URIs. The code expires in 10 minutes.

json
{
  "apiVersion": "v1",
  "kind": "DeviceAuthorization",
  "data": {
    "deviceCode": "d5c1…",
    "userCode": "BQTV-XKGF",
    "verificationUri": "https://pulse.superposition.app/cli/authorize",
    "verificationUriComplete": "https://pulse.superposition.app/cli/authorize?code=BQTV-XKGF",
    "expiresIn": 600,
    "interval": 5
  },
  "meta": { "requestId": "req_01j8zqf7" }
}

POST/api/v1/cli-auth/token

Poll with the deviceCode every interval seconds until the administrator approves in the browser. The response then carries the bearer session token.

json
{
  "apiVersion": "v1",
  "kind": "CliSession",
  "data": {
    "token": "pulse_cli_…",
    "tokenType": "Bearer",
    "expiresAt": "2026-10-16T00:00:00Z",
    "scopes": ["monitors:read", "monitors:write", "…"]
  },
  "meta": { "requestId": "req_01j8zqf7" }
}

POST/api/v1/cli-auth/revoke

Revoke the calling installation, its CLI sessions, and descendant API tokens. This is what pulsectl auth unlink uses.

authenticated
bash
pulsectl auth unlink --yes

Database health

GET/api/v1/database-health

Storage budget usage, per-category byte counts, retention windows for each data category, and the governor's mode (FULL_DETAIL through ESSENTIALS_ONLY). Health is one of HEALTHY, WATCHING, OPTIMIZING, PROTECTING, CRITICAL, or UNKNOWN.

config:read
json
{
  "apiVersion": "v1",
  "kind": "DatabaseHealth",
  "data": {
    "health": "HEALTHY",
    "summary": "Storage remains within its configured budget",
    "budgetBytes": 500000000,
    "usedBytes": 118000000,
    "governor": {
      "mode": "FULL_DETAIL",
      "action": "None required",
      "lastCompactionAt": "2026-07-18T03:17:00Z"
    }
  },
  "meta": { "requestId": "req_01j8zqf7" }
}

POST/api/v1/database-health/refresh

Request a new usage measurement. Measurements are cached for 15 minutes, so a call inside that window returns the cached snapshot. data.refresh.status reports which you got: CURRENT, CACHED, or STALE_FALLBACK.

config:write
bash
curl -X POST https://pulse.superposition.app/api/v1/database-health/refresh \
  -H "Authorization: Bearer $PULSE_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
esc