Claw Alarm

Read, create, update, snooze, and delete alarms on a user's iPhone running ClawAlarm. Use when the user asks to set an alarm, change an existing alarm's time/recurrence/label, snooze or disable an alarm, list their current alarms, or pair a new device. The skill talks to the ClawAlarm cloud-sync API and a silent APNS push reconciles the change onto the phone.

Audits

Pass

Install

openclaw skills install claw-alarm

Configure User Alarm

Overview

This skill lets you set and manage alarms on a user's iPhone running the ClawAlarm app.

ClawAlarm is a cloud-synced alarm clock. Each install registers a per-device Durable Object on a Cloudflare Worker (api.claw-alarm.com), and any change made through the API is fanned out to the phone over a silent APNS push. The phone then re-fetches the alarm list and reconciles it into AlarmKit (the iOS native alarm framework). End result: when you POST /v1/account/alarms from this skill, the phone rings on schedule — no app foreground required.

You can use this skill to:

  • List the user's current alarms.
  • Create a new alarm (one-off, daily, weekdays, weekends, or arbitrary weekday mask).
  • Update an existing alarm's time, label, recurrence, sound, or enabled state.
  • Snooze a currently-ringing alarm (intent-driven snoozes only).
  • Delete an alarm.
  • Inspect device status (last sync time, push health, pending revision).

All interaction goes through scripts/alarm-api.sh, a thin curl wrapper over the API. The CLI fetches the live OpenAPI spec from /openapi.json so the available routes, request bodies, and responses always reflect the deployed worker — there is no separately-versioned client to drift.

The full machine-readable spec lives at:

  • Spec: https://api.claw-alarm.com/openapi.json
  • Reference UI: https://api.claw-alarm.com/reference (Scalar-rendered)

Pairing & authentication

Before this skill can configure anything, the phone has to hand the CLI a bearer token. ClawAlarm uses a one-time pairing-code handshake to do that.

How the pairing handshake works

  1. On the phone: the user opens the ClawAlarm app and taps "Connect Claude" (or whatever the equivalent setting is in the current build). The app calls POST /v1/pairing/init, which mints a fresh accountId, a long-lived bearer token, and a 6-letter code of the form ABC-DEF. The phone stores the token locally and displays the code to the user.

  2. In this skill: the user pastes the code into the chat. You exchange it for the same token by running:

    scripts/alarm-api.sh pair ABC-DEF
    

    Internally, that runs POST /v1/pairing/exchange with {"code":"ABC-DEF"}, validates the returned token by hitting GET /v1/account/status, and writes it to .env next to the skill (or ~/.claw-alarm-cli/.env with --location=global).

  3. From here on, every API request from alarm-api.sh carries Authorization: Bearer <token> and lands on the same Durable Object as the phone's own writes.

Codes are single-use and expire 10 minutes after issue. If the user takes too long, ask them to refresh on the phone (or — if you're already paired and just want to add another non-iOS client — run scripts/alarm-api.sh refresh, which mints a new code on the existing account).

One token = one device

A token is bound to exactly one ClawAlarm install. The Durable Object key is derived from the accountId baked into the token; there is no notion of "account with multiple devices" in the current API.

That means:

  • To configure alarms on one phone, do the pairing handshake once.
  • To configure alarms on a second phone, do the pairing handshake again from a separate location. There's no shared state — each device has its own alarm list and its own token.
  • The token does not expire on a timer. It's invalidated only by the user explicitly resetting pairing on the device.

Storing the token

The bearer token is a secret in the cryptographic sense — anyone holding it can read and rewrite the alarm list on the paired device. However:

  • It can only configure alarms. It cannot read contacts, location, photos, payment info, or anything else on the phone.
  • Its blast radius is one device.
  • It does not roll. Re-pairing is cheap (one tap on the phone).

Given that, it is safe to commit the token to a project-local CLAUDE.md, an .env, or any other config file you'd normally pin to a repo for personal-project use. You do not need a secrets manager. The default alarm-api.sh pair command writes the token to <skill>/.env, which the skill .gitignores, but you can also paste it into your CLAUDE.md so future sessions auto-load it without re-pairing:

# In ~/.claude/CLAUDE.md or project CLAUDE.md
CLAW_ALARM_API_TOKEN=<paste-token-here>

The CLI resolves credentials in this order:

  1. CLAW_ALARM_API_TOKEN from the current shell environment.
  2. Local saved token at <skill>/.env.
  3. Global saved token at ~/.claw-alarm-cli/.env.

Use scripts/alarm-api.sh auth status to see which one is active.

Listing & updating alarms

Once authenticated, the CLI is a generic curl wrapper. Every endpoint on the deployed worker is reachable through it — the help text is generated from the live OpenAPI spec, so anything documented there is callable here.

Discover available routes

# All routes, with example payloads inferred from the spec.
scripts/alarm-api.sh --help

# Full schema for one route (params, request body, responses).
scripts/alarm-api.sh --help /v1/account/alarms
scripts/alarm-api.sh --help /v1/account/alarms/{id}

Common workflows

List alarms:

scripts/alarm-api.sh /v1/account/alarms

Returns { object: "list", data: [Alarm, ...], device_revision: <int> }. The device_revision is the per-device monotonic counter — clients can short-circuit a refresh when it hasn't changed.

Create a daily 7:30 am alarm:

scripts/alarm-api.sh -m POST -d '{
  "label": "Wake up",
  "local_time": { "hour": 7, "minute": 30 },
  "recurrence": { "type": "daily" },
  "time_zone": "America/Los_Angeles"
}' /v1/account/alarms

Create a weekdays-only alarm:

scripts/alarm-api.sh -m POST -d '{
  "label": "Stand-up",
  "local_time": { "hour": 9, "minute": 0 },
  "recurrence": { "type": "weekdays" },
  "time_zone": "America/Los_Angeles"
}' /v1/account/alarms

Create an arbitrary-weekday alarm (e.g. Mon/Wed/Fri):

recurrence.weekday_mask is a 7-bit bitmask. Bit 0 = Sunday, bit 1 = Monday, …, bit 6 = Saturday. Mon (2) | Wed (8) | Fri (32) = 42.

scripts/alarm-api.sh -m POST -d '{
  "label": "Gym",
  "local_time": { "hour": 6, "minute": 15 },
  "recurrence": { "type": "weekly_mask", "weekday_mask": 42 },
  "time_zone": "America/Los_Angeles"
}' /v1/account/alarms

Create a one-off alarm:

scripts/alarm-api.sh -m POST -d '{
  "label": "Flight",
  "local_time": { "hour": 4, "minute": 45 },
  "recurrence": { "type": "none" },
  "one_off_local_date": { "year": 2026, "month": 5, "day": 12 },
  "time_zone": "America/Los_Angeles"
}' /v1/account/alarms

Update an alarm:

PATCH only the fields you want to change. Pass the alarm's current revision as If-Match to detect concurrent edits from the phone.

scripts/alarm-api.sh -m PATCH -d '{
  "local_time": { "hour": 8, "minute": 0 }
}' /v1/account/alarms/<id>

To use optimistic concurrency, add the header manually with curl, or omit it for a last-write-wins update on a known-stable alarm.

Disable / re-enable an alarm:

scripts/alarm-api.sh -m PATCH -d '{ "enabled": false }' /v1/account/alarms/<id>

Snooze a ringing alarm (intent-driven only):

The body is an ISO instant, not a duration — clock skew between client and server otherwise turns a 5-minute snooze into 4:55 or 5:05.

scripts/alarm-api.sh -m POST -d '{
  "snoozed_until": "2026-05-04T07:35:00Z"
}' /v1/account/alarms/<id>/snooze

Delete an alarm:

scripts/alarm-api.sh -m DELETE /v1/account/alarms/<id>

Inspect device status:

scripts/alarm-api.sh /v1/account/status

Includes last_synced_with_device, device_revision, last_push_sent_at, last_push_failure_code, and next_fire_at (server-computed preview of the next scheduled fire across all enabled alarms).

Important schema notes

Authoritative shapes live in the OpenAPI spec — always prefer alarm-api.sh --help <route> over guessing — but a few things are worth highlighting because they're easy to get wrong:

  • local_time is local-clock, not UTC. Recurring alarms store a clock-time spec (hour/minute) plus a recurrence rule plus a time_zone. The device recomputes the next absolute fire date each cycle, which keeps daily and weekday alarms DST- and travel-correct. Do not pretranslate the hour to UTC.
  • time_zone is required and must be an IANA name (America/Los_Angeles, Europe/Zurich, etc.). When the user asks for "7am" without specifying a zone, default to the zone the phone is currently in if you can infer it from /v1/account/status's next_fire_at; otherwise ask.
  • one_off_local_date is required iff recurrence.type === "none". The schema doesn't enforce that pairing — the repository does — so the API will reject the wrong combination with BadRequest.
  • Field names on the wire are snake_case. The rest of the worker codebase is camelCase; the wire uses Stripe-style snake_case so any future non-TS client reads naturally.
  • Mutating endpoints accept an optional If-Match: <revision> header for optimistic concurrency. The CLI's -d shorthand only sets the body, not arbitrary headers — use raw curl if you need it.

Errors you'll see

The error union is declared in src/api/errors.ts on the worker. The shapes are:

  • 400 BadRequest — malformed payload, wrong recurrence/date combination, invalid weekday_mask, etc.
  • 401 Unauthorized — missing/invalid bearer token.
  • 404 NotFound — alarm id doesn't exist on this device.
  • 409 ConflictIf-Match revision didn't match the stored revision.
  • 410 PairingCodeExpired / 404 PairingCodeNotFound — pairing-handshake failures.
  • 500 InternalServerError — bug; report and retry.

Where the docs come from

The ClawAlarm worker is built on @effect/platform's HttpApi, which means the OpenAPI spec is generated from the same HttpApi definitions that produce the request/response handlers. There is no separate spec to keep in sync — they are the same artifact.

If a route exists, it's in /openapi.json. If a route is in /openapi.json, it's callable. The Scalar UI at /reference is a renderer over that spec.

When investigating an unfamiliar route, the right order is:

  1. scripts/alarm-api.sh --help <route> — fast, machine-readable, scoped.
  2. https://api.claw-alarm.com/reference — interactive, full surface, shows examples.
  3. The worker source under cloudflare-worker/src/api/groups/ and cloudflare-worker/src/api/schemas/ — only when behavior is unclear from the schema alone (e.g. cross-field invariants like "one_off_local_date is required iff recurrence.type === 'none'").

Local testing

Point the CLI at a local wrangler dev instance:

CLAW_ALARM_API_BASE_URL=http://127.0.0.1:8787 scripts/alarm-api.sh pair ABC-DEF
CLAW_ALARM_API_BASE_URL=http://127.0.0.1:8787 scripts/alarm-api.sh /v1/account/alarms

The base URL is read fresh on every invocation, so you can flip between local and production by toggling the env var.