Maintenance windows and snooze

Planned work makes noise: a deploy restarts services, a database upgrade trips every check. A maintenance window mutes a group while it runs. A snooze mutes one noisy alert for a while.

What a window does

While a group is in a maintenance window:

  • Alerts are still stored. You see them in the app, marked in maintenance.
  • Nobody is notified: no push, no call, no SMS, no email.
  • Escalation waits. Alerts that were already escalating don't move on to the next person.
  • When the window ends (on time or ended early), every alert that is still open is notified again, once, and its escalation starts. Alerts you acked or closed in the meantime stay quiet.

Rules:

  • A window is at most 7 days long, and can be scheduled up to 90 days ahead.
  • A group has at most 20 running or upcoming windows. Windows may overlap; the group stays muted until the last one ends.
  • Times in the API are UTC (ISO 8601, like 2026-10-22T06:00:00Z). Any explicit zone is accepted on input (2026-10-22T08:00:00+02:00); a time without a zone is refused. The apps and the admin app show your local time.

Start one with your key

Use the group's send key, the same one you send alerts with. This is what a deploy script or CI job does: it mutes only the group that key belongs to.

# Mute for 30 minutes, starting now
curl -H 'X-ZapaPush-Key: YOUR_KEY' -d minutes=30 https://api.zapapush.com/maintenance

# With a reason (shown in the app and the admin app)
curl -H 'X-ZapaPush-Key: YOUR_KEY' -d minutes=30 -d reason='deploy v2.4' https://api.zapapush.com/maintenance

# Until a time (UTC), as JSON
curl -H 'X-ZapaPush-Key: YOUR_KEY' -H 'Content-Type: application/json' \
  -d '{"until": "2026-10-22T06:00:00Z", "reason": "db upgrade"}' \
  https://api.zapapush.com/maintenance

# Scheduled: Saturday night, 02:00 to 04:00 UTC
curl -H 'X-ZapaPush-Key: YOUR_KEY' -H 'Content-Type: application/json' \
  -d '{"starts_at": "2026-10-24T02:00:00Z", "ends_at": "2026-10-24T04:00:00Z", "reason": "kernel updates"}' \
  https://api.zapapush.com/maintenance

Give one of:

Field Meaning
minutes Starts now, ends after this many minutes (1 to 10080).
until Starts now, ends at this time.
starts_at, ends_at A scheduled window. A starts_at in the past means now.

reason is optional (up to 200 characters). The key works the same ways as for sending: the X-ZapaPush-Key header (best), Authorization: Bearer YOUR_KEY, or Basic auth with the key as the password.

The answer is 201 with the window:

{
  "id": 42,
  "group_id": 7,
  "starts_at": "2026-10-22T05:30:00.000Z",
  "ends_at": "2026-10-22T06:00:00.000Z",
  "reason": "deploy v2.4",
  "active": true,
  "created_by": { "key": true },
  "created_at": "2026-10-22T05:30:00.120Z",
  "ended_at": null
}

List windows

curl -H 'X-ZapaPush-Key: YOUR_KEY' https://api.zapapush.com/maintenance
{
  "group_id": 7,
  "in_maintenance": true,
  "until": "2026-10-22T06:00:00.000Z",
  "windows": [ { "id": 42, "active": true, "starts_at": "…", "ends_at": "…", "reason": "deploy v2.4", "…": "…" } ]
}

Only running and upcoming windows are listed, soonest first.

End or cancel early

# End one window now (or cancel it, if it hasn't started)
curl -X DELETE -H 'X-ZapaPush-Key: YOUR_KEY' https://api.zapapush.com/maintenance/42

# End every running window of the group and cancel the upcoming ones
curl -X DELETE -H 'X-ZapaPush-Key: YOUR_KEY' https://api.zapapush.com/maintenance

Ending a running window notifies the alerts still open right away. The answer carries "status": "ended" or "cancelled"; a window that is already over, or belongs to another group, is 404.

Starting and ending a window each count as one post toward the key's 60 posts per minute.

Wrap a deploy in CI

Start a window before the deploy and end it afterwards, even if the deploy fails. Give the window a safe length (here 30 minutes): if the job dies before its last step, the window still ends on its own.

Store the key as a secret named ZAPAPUSH_KEY in your CI, never in the pipeline file.

GitHub Actions

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      ZAPAPUSH_KEY: ${{ secrets.ZAPAPUSH_KEY }}
    steps:
      - uses: actions/checkout@v4
      - name: Mute ZapaPUSH alerts
        run: curl -fsS -m 10 -H "X-ZapaPush-Key: ${ZAPAPUSH_KEY}" -d minutes=30 -d "reason=deploy ${GITHUB_SHA::7}" https://api.zapapush.com/maintenance
      - name: Deploy
        run: ./deploy.sh
      - name: Unmute ZapaPUSH alerts
        if: always()
        run: curl -fsS -m 10 -X DELETE -H "X-ZapaPush-Key: ${ZAPAPUSH_KEY}" https://api.zapapush.com/maintenance

GitLab CI

Add ZAPAPUSH_KEY under Settings → CI/CD → Variables (masked).

deploy:
  stage: deploy
  before_script:
    - curl -fsS -m 10 -H "X-ZapaPush-Key: ${ZAPAPUSH_KEY}" -d minutes=30 -d "reason=deploy ${CI_COMMIT_SHORT_SHA}" https://api.zapapush.com/maintenance
  script:
    - ./deploy.sh
  after_script:
    # Runs even when the script fails.
    - curl -fsS -m 10 -X DELETE -H "X-ZapaPush-Key: ${ZAPAPUSH_KEY}" https://api.zapapush.com/maintenance

Gitea Actions

Same syntax as GitHub Actions; add ZAPAPUSH_KEY under the repository's Settings → Actions → Secrets.

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      ZAPAPUSH_KEY: ${{ secrets.ZAPAPUSH_KEY }}
    steps:
      - uses: actions/checkout@v4
      - run: curl -fsS -m 10 -H "X-ZapaPush-Key: ${ZAPAPUSH_KEY}" -d minutes=30 -d "reason=deploy ${GITHUB_SHA::7}" https://api.zapapush.com/maintenance
      - run: ./deploy.sh
      - if: always()
        run: curl -fsS -m 10 -X DELETE -H "X-ZapaPush-Key: ${ZAPAPUSH_KEY}" https://api.zapapush.com/maintenance

Any shell script

#!/bin/sh
set -e
curl -fsS -m 10 -H "X-ZapaPush-Key: ${ZAPAPUSH_KEY}" -d minutes=30 -d reason=deploy https://api.zapapush.com/maintenance >/dev/null
trap 'curl -fsS -m 10 -X DELETE -H "X-ZapaPush-Key: ${ZAPAPUSH_KEY}" https://api.zapapush.com/maintenance >/dev/null' EXIT
./deploy.sh

In the admin app

Open Groups at app.zapapush.com and open a group. Under Maintenance, group admins and the company's owner can start a window for 30 minutes, 1 hour or 2 hours, until a time, or schedule one, and end or cancel any window. Every member sees the windows, and the group shows a maintenance badge while one runs. Times are in your local time (or UTC, if you chose it under Account).

With a session (apps and admin app)

The apps and the admin app use the same API with your sign-in instead of a key. Any member of a group lists its windows; group admins and the company's owner start and end them.

curl -H "Authorization: Bearer $TOKEN" https://api.zapapush.com/v1/groups/7/maintenance
curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"minutes": 60, "reason": "deploy"}' \
  https://api.zapapush.com/v1/groups/7/maintenance
curl -X DELETE -H "Authorization: Bearer $TOKEN" https://api.zapapush.com/v1/groups/7/maintenance/42

# The company's owner: the same window for every group of the company
curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"minutes": 60}' \
  https://api.zapapush.com/v1/companies/3/maintenance

GET /v1/groups also shows maintenance_until for each group (null when it isn't in maintenance).

Snooze an alert

A snooze mutes one alert: repeats update it quietly and its escalation waits. If it is still open when the snooze ends, you are notified again, once. Any member of the alert's group can snooze it, for 15 minutes, 1 hour, 4 hours or 1 day.

# Snooze alert 1234 for an hour
curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"minutes": 60}' \
  https://api.zapapush.com/v1/alerts/1234/snooze

# Un-snooze it: escalation carries on
curl -X DELETE -H "Authorization: Bearer $TOKEN" https://api.zapapush.com/v1/alerts/1234/snooze

minutes is one of 15, 60, 240, 1440. A closed alert can't be snoozed (409). Alerts in the API carry snoozed_until (UTC, or null) and in_maintenance (true while waiting for its group's window to end), and the alert's timeline (GET /v1/alerts/{id}/timeline) shows maintenance, snooze, unsnooze and renotify events.

Coming from OpsGenie or PagerDuty

You used In ZapaPUSH
OpsGenie maintenance policy (POST /v1/maintenance, time.type: for-1-hour or schedule) POST /maintenance with the group's key: minutes=60, or starts_at/ends_at.
OpsGenie Cancel / Change end date of a maintenance DELETE /maintenance/{id}, then start a new one with the new end.
OpsGenie alert snooze (POST /v2/alerts/{id}/snooze with endTime) POST /v1/alerts/{id}/snooze with minutes.
PagerDuty maintenance window (POST /maintenance_windows, services: [...]) One window per ZapaPUSH group (a group plays the part of a PagerDuty service), or POST /v1/companies/{id}/maintenance for every group.
PagerDuty incident snooze (POST /incidents/{id}/snooze, duration in seconds) POST /v1/alerts/{id}/snooze with minutes (15, 60, 240 or 1440).
PagerDuty: events during maintenance are dropped ZapaPUSH stores them (marked in maintenance) and notifies the open ones once when the window ends, so nothing slips through.

The OpsGenie and PagerDuty compatible endpoints (/v2/alerts, /v2/enqueue) follow the windows too: alerts they create during a window are stored, not sent.