Skip to content
Charming Docs
Esc
navigateopen⌘Jpreview
On this page

Routines

Routines run an app op on its own, on a timer, with no chat open.

What you can do

Run an op on a timer. A Routine invokes one of an app’s declared ops on its own, hourly, daily, or weekly, without a chat open and without the owner doing anything. Use it for a poll, a refresh, a daily check, or any op that should just keep running.

Up to three per app, twenty-five per account. Each app can carry up to 3 Routines; each account, up to 25 across every app. Both caps are fixed today, not configurable.

Stops itself after repeated failures. A Routine that fails 5 times in a row disables itself and emails the owner. Re-enabling it resets the failure count and starts the clock over.

Technical

A Routine invokes its target op with empty input ({}) on the schedule you set. It runs as the app itself, the same as any other server-side call — it does not count as the owner opening the app.

Copy this prompt for your agent

Set up a Routine for my Charming app. Read
https://charm.ing/docs/capabilities/routines.md first. Work out
which op should run on its own and how often, then create the
Routine and tell me when it'll first run.

How an agent performs this job

Use create_routine to schedule a declared op, list_routines to see the caller’s Routines and their run state, update_routine to change the interval or enable/disable one, and delete_routine to stop and remove one. The matching HTTP routes are POST /api/v1/apps/{appId}/routines (create), GET /api/v1/routines (list), PATCH /api/v1/routines/{routineId} (update), and DELETE /api/v1/routines/{routineId} (delete) — session-gated, and behaviorally identical to their MCP twins.

A worked example:

create_routine({ app_id: "<uuid>", op: "refreshFeed", interval: "hourly" })
→ Created Routine routine_<uuid>: runs `refreshFeed` hourly on <app>.

list_routines()
→ routine_<uuid> — `refreshFeed` on app <uuid>, hourly

If refreshFeed then fails on 5 ticks in a row, the Routine disables itself (enabled: false, disabled_reason: "auto"), the owner gets an email with a ready-to-paste fix prompt, and next_run_at reads null until it’s re-enabled with update_routine({ routine_id, enabled: true }).

The contract

interval is a closed enum: hourly, daily, or weekly — never raw cron syntax. The target op must accept empty input; an op with any required input field cannot be scheduled. create_routine and its HTTP twin reject a second Routine on the same (app, op) pair.

list_routines and every other Routines response return a routine object with id, app_id, op, interval, and seven run-state fields:

Field Meaning
enabled false once the Routine is owner-disabled or auto-disabled
disabled_reason "owner", "auto", or null when enabled
next_run_at Next scheduled run (ISO 8601), or null while disabled
last_run_at Last tick’s timestamp, or null before the first run
last_outcome Last tick’s result, or null before the first run
last_error { kind, message } for the last failure, or null
consecutive_failures Resets to 0 on any success or on re-enable

Four error kinds are specific to Routines:

Error kind Fires when
routine_limit_exceeded The per-app (3) or per-user (25) cap is already met
op_requires_input The target op declares a required input field, so it can’t run with empty input
duplicate_routine A Routine already exists for this (app, op) pair
invalid_interval Internal guard for the interval-to-cron mapping; not reachable through normal hourly/daily/weekly input

The generated MCP reference and REST API reference define the full request and response shapes.

Limits, access, and deletion

  • Caps. 3 Routines per app, 25 per account, hourly is the shortest interval available. See Limits.
  • Access. Creating, updating, and deleting a Routine requires the same app:write access as editing the app itself. See Privacy and sharing.
  • Deletion. Deleting the app or the account cascades to its Routines; there’s no separate cleanup step.
  • Not a Routine tick. An unattended tick never counts as the owner using the app — it doesn’t affect the app’s activity history the way an owner’s own call would.

Was this page helpful?