# Server routines and step events

A routine runs a finite recipe on Eggtimer servers. Each step has its own stored start and deadline. A slow webhook cannot extend the next step. A late server wake-up catches up on the original timeline; it does not pretend that delivery happened on time. Open [your workspace](https://eggtimer.app/automate.html), choose Run a routine, and start a preset or paste recipe JSON.

## Create through HTTP or MCP

POST https://eggtimer.app/api/server-routines with Authorization: Bearer YOUR_WORKSPACE_API_KEY and Content-Type: application/json. The request is bounded at 32 KiB.

```json
{"recipe":{"version":1,"title":"Focus 25/5","autoAdvance":true,"cycles":4,"steps":[{"label":"Focus","seconds":1500},{"label":"Break","seconds":300}]},"idempotency_key":"your-source-event-routine-1","events":["phase.completed"],"max_attempts":3,"max_lateness_seconds":300}
```

A new request schedules immediately and returns HTTP 201 with created:true and routine. An identical retry returns 200 with the same run and timeline; conflicting reuse returns 409. Add optional webhook_url only to authorize selected events to a private Zapier HTTPS Catch Hook. Never put that secret in a recipe or shared link. Without a hook, the routine still records its events for the API and Zapier polling.

Version 1 and 2 recipes use the [same schema](https://eggtimer.app/recipe.schema.json) as the browser. Server restrictions: automatic advancement, whole seconds of at least one second, no more than 64 expanded phases including cycles and repeats, total duration at most 30 days. Browser-only fractional beep-test pacing remains in the browser. Server routines record workflow events; they do not play the recipe's sounds or ring a phone.

Workspace MCP at https://eggtimer.app/api/mcp/server adds create_server_routine, get_server_routine, get_routine_events, cancel_server_routine, list_server_routines and list_server_events. Together with public discovery/link tools and one-shot timer/workspace tools, there are 18 tools. The public endpoint has seven; the legacy owner key has ten. AI clients must support Bearer headers and get explicit authorization before scheduling a routine or enabling a hook.

## Events and delivery

- routine.started: the run begins.
- phase.started / phase.completed: a step begins / finishes.
- cycle.completed: one complete pass through the recipe finishes.
- routine.completed: the entire finite recipe finishes.
- routine.cancelled: an ongoing run is cancelled.

All observed events enter the retained workspace feed. The events array controls hook delivery only; default is phase.completed. Selecting both the final phase completion and routine completion produces two different events. Each event has a stable id/event_id, routine_id/run_id, recipe_revision, generation, sequence, phase_index, phase_label, cycle, step, repetition, scheduled_at, observed_at, and run/phase/event URLs. scheduled_at is the intended transition time; observed_at is when the server first processes that event, persisted once across retries. completed_at is when the server processes completion. These times can be equal at millisecond precision and do not measure webhook delivery; use delivery.accepted_at for receiver acceptance. Cloudflare clocks advance after I/O, so equal timestamps do not imply a fabricated scheduled timestamp. Sequence describes timeline order within that run, not network delivery order.

Each selected event has its own persisted delivery outbox. HTTPS POST, no redirects, ten-second timeout. max_attempts is 1–5, default 3. Transient HTTP/network failures retry after 5s, 30s, 120s and 600s, within the attempt limit. max_lateness_seconds is 1–86400, default 300, measured from scheduled_at. An event beyond that freshness window becomes expired and remains inspectable; it is not sent. An accepted response is persisted before reporting its receipt, so a failed receipt callback does not itself resend an accepted POST.

Delivery may be late, duplicated, out of order, or fail. Stable event_id and Idempotency-Key support receiver-side deduplication; a raw Catch Hook does not supply that deduplication automatically. Network timeouts and process interruption can leave an unknown outcome. accepted means the hook returned HTTP success, not that later Zap actions succeeded. There is no hard real-time or emergency guarantee.

## Inspect, link and cancel

GET /api/server-routines lists active/recent run summaries. GET /api/server-routines/ID returns the stored recipe, phase plan and current state. GET /api/server-routines/ID/events returns observed events with their delivery outcomes. POST /api/server-routines/ID/cancel fences future claims, stops future phases and pending deliveries, and records cancellation once. Already claimed or in-flight HTTP can still complete; the response reports in_flight_may_complete. Cancelling a completed run stops outstanding attempts without inventing an unfinished-run cancellation event.

Each run has https://eggtimer.app/routines/ID, with #step=N or #event=EVENT_ID selecting a particular step or event in the signed-in view. These links contain no credential and require the owning workspace key. IDs survive labels; timelines are immutable. A new recipe or schedule creates a new run. Expired records cannot be recovered through their URL.

GET /api/server-events?limit=200 reads the newest workspace events. Optional before/after integer cursors and event type filters paginate. Use next_before until null; each page is newest first. id is the durable event identity, cursor is the feed position, stream_id identifies the feed incarnation. Delivery updates reuse the same event, so polling triggers do not fire again for receipt changes. A gap before oldest_cursor means older history may have expired. Consumers must track both stream_id and cursor, and tolerate bounded retention.

## Bounds and privacy

Timers and routines share 50 new runs/day (UTC) and ten active slots per workspace, including pending deliveries. Routines reserve all potential timeline events plus one cancellation event before creation: up to 500 reserved events/workspace/day, shared deployment cap 2,000/day. Reservations are not refunded after cancellation. Existing routines continue when new-run capacity fills. The maximum feed is 1,000 events and seven days; each run and its outboxes expire seven days after termination. Workspace idle expiry is 90 days. This is a free plan with usage limits, not an unbounded scheduler.

Recipes, phase labels and private hook destinations are operational data, separate from anonymous browser-start counts. Hook secrets are removed at terminal delivery/cancellation; the parent drops its copy after all outboxes are dispatched. Status and event APIs never return hooks or keys. Keep sensitive data out of labels. Closing a workspace blocks further deliveries and quietly cancels its routines; in-flight requests may still arrive.

[Workspace](https://eggtimer.app/automate.html) · [One-shot server timers](https://eggtimer.app/server-timers.md) · [OpenAPI](https://eggtimer.app/openapi.json) · [AI connection](https://eggtimer.app/ai.md) · [Comparisons](https://eggtimer.app/compare/)
