# Set up Pingtower for your user

This page is written for AI agents setting up Pingtower on a user's behalf.
Machine-readable companions: [/llms.txt](https://www.pingtower.com/llms.txt), this
page as markdown at [/agents/index.md](https://www.pingtower.com/agents/index.md),
the [API reference as markdown](https://www.pingtower.com/api/index.md), and the
[OpenAPI spec](https://www.pingtower.com/api/openapi.json) — generated from the
server's own route table and gated against drift, so it cannot describe a
contract the server does not keep.

## What Pingtower is — and is not

Log-driven alerting: the user's services POST structured log events, the
server collapses repeats by message shape, evaluates declarative rules, and
escalates until a human acknowledges. It is **not** an uptime prober: there
are no HTTP/TCP/ping checks and no heartbeat, so it cannot detect a service
that has stopped logging. If your user asked for uptime monitoring, tell
them that before setting anything up.

## Division of labor

You can do the entire server side end to end: install, account, tenant,
keys, project, source, rules, and verification. Only the human can install
the iOS app on their phone and redeem a pairing code. Finish with the
explicit handoff in the last step.

**Secrets discipline:** the account password, the `ptk_` API key, and the
`pti_` ingest token are each shown exactly once at creation — there is no
route that reads them back, only rotation. Capture them into the user's
secret store as they appear. Never write them into the repo, and don't echo
them into files you leave behind.

## Choose a plane

**Recommended: hosted.** The fastest path to a working setup is the hosted
service — no server to install or maintain. Set:

```sh
PT=https://api.pingtower.com
```

and skip straight to [bootstrap](#2-bootstrap-account-tenant-api-key). Only
self-host (step 1) when the user explicitly wants their alert data on their
own box.

## 1. Install the server (self-hosted)

Download the signed release for the box's architecture, verify it, and
refuse to run it if either check fails. Checksums are on the
[get-started page](https://www.pingtower.com/get-started/); the release public key is
[`pingtower-release.pub`](https://www.pingtower.com/dist/pingtower-release.pub).

```sh
case "$(uname -m)" in x86_64) arch=amd64 ;; aarch64|arm64) arch=arm64 ;; esac
curl -fsSLO https://www.pingtower.com/dist/pingtower-linux-$arch
curl -fsSLO https://www.pingtower.com/dist/pingtower-linux-$arch.minisig
curl -fsSLO https://www.pingtower.com/dist/pingtower-release.pub
minisign -Vm pingtower-linux-$arch -p pingtower-release.pub
```

Install it with a dedicated system user and a systemd unit:

```sh
sudo install -m 0755 pingtower-linux-$arch /usr/local/bin/pingtower
sudo useradd --system --home /var/lib/pingtower --shell /usr/sbin/nologin pingtower
sudo mkdir -p /var/lib/pingtower
sudo chown pingtower:pingtower /var/lib/pingtower
```

`/etc/systemd/system/pingtower.service`:

```ini
[Unit]
Description=pingtower
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=pingtower
Group=pingtower
ExecStart=/usr/local/bin/pingtower \
  -listen 127.0.0.1:8391 \
  -data-dir /var/lib/pingtower
Restart=on-failure
RestartSec=2
TimeoutStopSec=20
LimitNOFILE=65536
UMask=0077
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/pingtower
ProtectHome=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target
```

The daemon speaks plain HTTP on localhost and expects a reverse proxy to
terminate TLS. With Caddy, `/etc/caddy/Caddyfile`:

```text
alerts.example.com {
    reverse_proxy 127.0.0.1:8391
}
```

Start it and verify before going further:

```sh
sudo systemctl daemon-reload
sudo systemctl enable --now pingtower
curl -s http://127.0.0.1:8391/healthz
```

Useful flags: `-retain-days`, `-max-logs-per-source`, `-backup-dir`, and
`-secret-key` to seal integration credentials at rest (`pingtower -h` lists
them all).

## 2. Bootstrap: account, tenant, API key

Use the human's real email — it is how they will log in from the iOS app.

```sh
# Create the account. Returns 201 with a pts_ session token.
curl -s "$PT/v1/signup" \
  -H 'Content-Type: application/json' \
  -d '{"email":"user@example.com","password":"…"}'
# → {"account":{…},"token":"pts_…"}

SESSION=pts_…

# Create the tenant. Returns 201 with the tenant id every later call needs.
curl -s "$PT/v1/tenants" \
  -H "Authorization: Bearer $SESSION" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Acme"}'
# → {"id":"<tid>","name":"Acme","role":"owner"}

# Mint the API key. No body. Returns 201; the token is shown only once.
curl -s -X POST "$PT/v1/tenants/<tid>/keys" \
  -H "Authorization: Bearer $SESSION"
# → {"id":"…","token":"ptk_<tenant>_…"}
```

Token prefixes are the plane: `pts_` manages the account, `ptk_` reads and
configures everything, `pti_` may only call `/v1/ingest`. They are mutually
exclusive — one kind can never be mistaken for another.

## 3. Project, source, rule

```sh
API=ptk_…

curl -s "$PT/v1/projects" \
  -H "Authorization: Bearer $API" \
  -d '{"name":"demo"}'

# Returns the source's pti_ ingest token, shown only once.
# retain_logs:true keeps events readable via logtail — useful for debugging.
curl -s "$PT/v1/projects/demo/sources" \
  -H "Authorization: Bearer $API" \
  -d '{"name":"api","retain_logs":true}'
# → {"name":"api","token":"pti_<tenant>_…"}

# Alert on every error-or-worse event, renotifying every 15 minutes
# until acked.
curl -s "$PT/v1/projects/demo/rules" \
  -H "Authorization: Bearer $API" \
  -d '{"name":"errors","match":{"min_level":400},"renotify_minutes":15}'
```

The [API reference](https://www.pingtower.com/api/) covers richer rules (tag, source,
and shape matchers, key conditions, thresholds, escalation ladders),
integrations (webhook, Slack, Telegram), runbook actions, on-call schedules,
and incident reports.

## 4. Wire the user's services

Anything that can POST JSON can be a source — add this where the user's app
already logs errors. Levels are HTTP-ish: 100 debug, 200 info, 300 warn,
400 error, 500 critical. Put variable data in `keys` (it powers identifier
grouping); repeated messages dedup by shape into one alert with a count.

```sh
curl -s "$PT/v1/ingest" \
  -H "Authorization: Bearer pti_<tenant>_…" \
  -d '{"message":"payment failed","level":400,
       "keys":{"order":"o_123"},"tags":["billing"]}'
```

Want outside-in checks too — HTTP probes, certificate expiry, ping? The
[addons agent](/addons/index.md) installs on any box and reports its
measurements through this same ingest plane; its metric keys (like
`ttfb_ms` or `days_until_expiry`) threshold directly in the rules you just
created.

## 5. Verify end to end

Do not report success on wiring alone — prove the pipeline. Send an event
that matches the rule, watch it become an alert, then ack it:

```sh
# Returns 202 {"alerted":true,"template_id":"…"} when a rule matched.
curl -s "$PT/v1/ingest" \
  -H "Authorization: Bearer pti_<tenant>_…" \
  -d '{"message":"agent setup verification","level":400}'

# The alert appears here with an id.
curl -s "$PT/v1/pull?cursor=0" -H "Authorization: Bearer $API"

# Ack it so the verification doesn't keep renotifying.
curl -s -X POST "$PT/v1/alerts/<id>/ack" -H "Authorization: Bearer $API"
```

Only after the alert shows up in `/v1/pull` tell the user the setup works.

## 6. Hand off to the human

Tell the user, concretely:

- **Where their tokens are** — which secret store holds the API key and
  ingest token, and that the tokens cannot be read back, only rotated.
- **What alerts when** — the rules you created and their renotify cadence.
- **Get the iOS app** —
  it is not on
  the App Store yet. Until then, webhook, Slack, and Telegram
  integrations deliver alerts.
- **Pairing for phone push (self-hosted only)** — paging a phone through
  the iOS app needs a pingtower.com account paired to the box: running
  `pingtower pair --email user@example.com` on the server prints a one-shot
  code the human redeems in the app. Apple push for the App Store app can
  only be sent by pingtower.com; everything else stays on their box.

