Routines
Have your apps do work on a schedule, like a nightly sync or a weekly summary, set up from the console, the CLI or your coding assistant.
A routine is work your app does on a schedule, without anyone opening it. Every morning at 7, email yesterday's orders. Every hour, clear out abandoned carts. Every Monday, send the team a summary.
You don't need an outside scheduling service for any of this. The White Ghost platform calls your app on time, and your app does the work.
How a routine works
A routine has three parts:
- a name, like
daily-summary; - a schedule: how often it runs, and in which timezone;
- a path in your app, like
/api/jobs/daily-summary.
When it's time, the platform calls that path on your app. Each call is a run, and every run is recorded: when it ran, how long it took and whether it worked.
A routine only ever calls its own app. There's no web address to fill in, only a path, so a routine can't reach another app or an outside website.
Anyone who can work on an app, its owner and its collaborators, can create and manage its routines.
Create a routine
In the console
Open Routines in the sidebar and click New routine. Or open an app, go to its Routines tab and click New routine there, which picks that app for you.
- App: the app the routine calls. You can pick any app you work on.
- Name: what you'll recognize it by, like
nightly-sync. Each name is unique in its app, and it can't be changed later. - Path: the path on your app, like
/api/jobs/nightly. The app's address is already filled in in front of it, so you only type the path. - Schedule: every few minutes, every hour, every day at a time, every week on a day, or a custom schedule. The dialog shows the next three runs, so you can check it before you save.
- Timezone: this starts as yours. Pick the one the schedule should follow.
- Time limit per run: how long your app gets to answer. One minute unless you choose otherwise, up to five minutes.
Schedules your plan doesn't cover are greyed out, so you only pick from what your plan runs.
From the terminal
Make sure your CLI is up to date: npm i -g ghosty-cli@latest. Then, for example, to call /api/jobs/daily-summary every day at 7:00, Bogotá time:
ghosty routines add --app orders --name daily-summary \
--path /api/jobs/daily-summary --schedule "0 7 * * *" --timezone America/Bogota
--timezone is your computer's timezone unless you say otherwise. --timeout sets the time limit per run in seconds: 60 unless you say otherwise, at most 300.
With your coding assistant
Ask for the outcome, in your own words: "every weekday at 8am, sync the price list." Your assistant writes the work into your app, publishes it, creates the routine and runs it once to check that it works. It can do all of this on its own once it's connected to White Ghost (see Connect your coding assistant).
Schedules and timezones
A schedule uses the standard five-field form: minute, hour, day of the month, month, day of the week. The console writes it for you. The terminal and the console's Custom schedule take it as text:
| Schedule | Runs |
|---|---|
0 7 * * * |
every day at 7:00 |
0 6 * * 1-5 |
every weekday at 6:00 |
0 9 * * 1 |
every Monday at 9:00 |
0 * * * * |
every hour, on the hour |
*/15 * * * * |
every 15 minutes |
If you build with a coding assistant, just say "every day at 7am" and it writes the schedule for you.
Times follow the timezone you choose for the routine. "Every day at 7:00" in America/Bogota runs at 7:00 in Bogotá, and a routine in a timezone with daylight saving keeps its local time when the clocks change.
The platform also takes care of the awkward cases:
- No pile-ups. If a run is still going when the next one is due, the next one is skipped instead of starting on top of it.
- No backlog. If runs were missed, only the latest one runs, never a burst of catch-up runs.
- Sleeping apps are skipped. While an app is asleep its routines don't run. They pick up again at their next time once you wake it.
- A time limit per run. One minute unless you give the routine more, up to five minutes. A run that takes longer is recorded as failed.
How your app receives a routine
Your app handles routines with its routines helper. Register the routine's path with it and write what should happen.
Node, in backend/src/index.ts or a file it imports, so it's registered when the app starts:
import { onRoutine } from './lib/routines';
onRoutine('/api/jobs/daily-summary', async ({ scheduledFor }) => {
await sendDailySummary({ day: scheduledFor.slice(0, 10) });
});
Python, in a module that backend/src/main.py imports:
from src.routines import on_routine
@on_routine("/api/jobs/daily-summary")
def daily_summary(run):
send_daily_summary(day=run.scheduled_for[:10])
Your handler gets the routine's name, the run's id, and when the run was due (scheduledFor / scheduled_for, in UTC). The helper answers for you: the run is recorded as succeeded when your handler finishes, and as failed when it throws an error. The error's details go to your app's logs, never into the answer.
Register the same path the routine calls. A query string in the routine's path (/api/jobs/sync?full=1) is fine: register just the path part (/api/jobs/sync).
How a call is checked
Every call from the platform is signed with a key that belongs to that one app. The helper checks the signature before your code runs, and refuses with a 401 any call that is:
- unsigned or altered;
- more than five minutes old;
- meant for a different path;
- signed for a different app.
Your handler never sees those calls. So a routine's path is safe to leave reachable: nobody else can trigger your routine by visiting it. You never handle the signature yourself; the helper does it on every call.
This also means routines work whatever your app's access mode (see Who can use your app). Paths registered with the helper answer the platform's signed calls before the sign-in check. Everything else in your app still asks visitors to sign in, as usual. That's why a routine should always go through the helper: in an app only workspace members can open, an ordinary route sends the platform to the sign-in page, and the run fails.
Good habits
- Make it safe to run twice. Anyone who manages the app can press Run now, so the same work can happen more than once. Record what a run covers (for example the day in
scheduledFor) and skip work that's already done. - Answer quickly. Do quick work directly. Split long work, like a big export or thousands of emails, into batches, and let each run do the next batch.
- Let failures fail. Throwing an error marks the run as failed, so you see it. If you catch everything and report success, you hide the problem.
- Keep the path for the platform. Don't also use a routine's path for a page or a button in your app.
Apps that need a one-time update
If an app's code doesn't have the routines helper yet, its Routines tab and its routines' pages say This app needs a one-time update before routines can run, and its routines show Waiting for app update in the list. Until the update is live, the platform doesn't call the app: each run is recorded as Blocked. The routine keeps its schedule, so nothing is lost.
The update is one command in the app's folder, then a deploy:
npm i -g ghosty-cli@latest
ghosty routines setup
ghosty deploy
ghosty routines setup adds the routines helper to your app, connects it in the app's entry file, and adds the routines guide for your coding assistant. It only changes files in that folder, and running it again is safe. If it can't find a safe place to connect the helper, it leaves that file as it is and tells you the exact lines to add, and where.
Prefer to hand it over? Press Copy instructions for your AI assistant on the app's Routines tab and paste them into your assistant, in the app's folder. Once the updated version is live, the routines run again at their next scheduled time.
Run history and Run now
Click a routine's name to open its page. It shows the full address the routine calls, its schedule, timezone and time limit, and when it runs next. Below that, every run, newest first:
- Time: when the run started.
- Trigger: Scheduled, or Run now when someone started it by hand.
- Status:
- Succeeded: your app answered with success.
- Failed: your app answered with an error, took longer than the time limit, or couldn't be reached.
- Skipped: the previous run was still going, the routine was paused, or the app was asleep.
- Blocked: the platform didn't call the app, for example because it isn't live.
- Running: the run is still going.
- HTTP: the status code your app answered with.
- Duration: how long the run took.
- Details: what went wrong, when something did. Expand a row to see all of it.
Runs are kept for 30 days. The Routines list also shows each routine's last run and next run at a glance.
On a routine's page:
- Run now calls your app straight away and tells you how the run went. It's the quickest way to test a routine. It works on a paused routine too, and you can run the same routine again after a few seconds.
- Pause stops the schedule until you Resume it. The routine keeps its settings and its run history. The switch on each row of the list does the same.
- Change edits the path, schedule, timezone and time limit. To rename a routine, create a new one and delete the old one.
- Delete removes the routine and its run history. Your app isn't changed.
Deleting an app deletes its routines too.
Every command and tool
| Command | What it does |
|---|---|
ghosty routines |
Lists the routines on every app you work on. Add --app <app> for one app. |
ghosty routines add |
Creates a routine (see From the terminal above). |
ghosty routines run <name> |
Runs it right now and shows the result, like Run now. |
ghosty routines runs <name> |
Shows its recent runs, newest first. |
ghosty routines pause <name> |
Stops it running on schedule. It keeps its settings and history. |
ghosty routines resume <name> |
Puts it back on its schedule. |
ghosty routines rm <name> |
Removes the routine and its run history. |
ghosty routines setup |
Gives an app its routines helper (see Apps that need a one-time update). |
Inside an app's folder you can leave out --app. To change a routine's path or schedule, use the console or ask your coding assistant.
For scripts and automations:
- Every command accepts
--jsonfor machine-readable output, and none of them ever stops to ask a question. ghosty routines runexits with an error status unless the run succeeded. Soghosty routines run daily-summary --app orders && echo doneonly says done when your app really did the work.- If your plan doesn't allow a routine, the command says which limit it hit and exits with status 4.
When your coding assistant is connected to White Ghost (ghosty mcp), it has the same abilities as tools:
| Tool | What it does |
|---|---|
list_routines |
Lists routines, for one app or for all of yours. |
set_routine |
Creates a routine, or changes one: its path, schedule, timezone, time limit, or whether it's paused. |
run_routine |
Runs a routine right now and reports the result. |
list_routine_runs |
Shows a routine's run history. |
delete_routine |
Removes a routine. |
setup_routines |
Gives an app its routines helper. |
You don't need to name the tools. Ask for the outcome, and your assistant picks them.
Plan limits
Your plan sets how many routines you can have and how often they can run.
| Free | Solo | Team | Scale | Enterprise | |
|---|---|---|---|---|---|
| Routines | 2 | 5 | 10 | 25 | agreed with you |
| Most frequent schedule | every hour | every hour | every 5 minutes | every 5 minutes | every minute |
The most frequent schedule is the shortest gap your plan allows between two runs of a routine. On a plan that runs routines every hour at most, a routine can run every hour, every day or every week, but not every 15 minutes.
Plan & usage in the console shows how many routines you have against your plan's limit, and the most frequent schedule your plan runs. See Pricing for everything else each plan includes.
How routines are counted
Paused routines count too, because a paused routine keeps its place and you can switch it back on at any time.
- On a personal plan (Free and Solo), the routines are yours: the count covers the routines on the apps you own.
- In a company workspace (Team, Scale and Enterprise), the routines are one shared pool for the whole workspace. The count covers every routine on every app in it, whoever built it, and everyone in the workspace sees the same number in Plan & usage. When a teammate deletes a routine they no longer need, that room is everyone's.
Need more?
- More routines. On Solo and in company workspaces, add the +5 routines pack from Plan & usage, under Extra capacity, the same way as any other pack. In a company workspace, the extra routines join the shared pool. A pack gives you more routines. It doesn't change how often they can run.
- Routines that run more often than every 5 minutes are available on request. Write to hello@whiteghost.ai and we'll set it up with you.
A schedule your plan doesn't cover
Your plan is checked when you create a routine, change its schedule or resume it. One routine too many, or a schedule that runs more often than your plan allows, is refused when you save it, and the message tells you which limit you hit. Pick a less frequent schedule, or make room, and it saves straight away.
When your plan changes
If your plan changes and no longer covers all of your routines, White Ghost pauses the ones it doesn't cover rather than run them outside your plan. Nothing is deleted: each routine keeps its settings and its run history.
- Runs too often. A routine whose schedule runs more often than the plan allows is paused.
- Too many routines. The oldest routines keep running, up to the number the plan includes. Newer ones beyond that are paused. In a company workspace this counts across the whole workspace.
Each person with a paused routine gets one email that lists their paused routines, why each one was paused, and what turns it back on. In the console, a paused routine shows Paused by plan and the reason. ghosty routines marks it [paused by your plan].
To turn one back on, give it a less frequent schedule, or delete a routine you no longer need, then resume it. A plan that covers it works too. Resuming always checks your plan first, so a routine stays paused while it still doesn't fit, and the message tells you which limit.
Troubleshooting
A run failed with "The app's access gate asked for sign-in". The app only lets workspace members in, and the routine's path isn't registered with the routines helper, so the call reached the sign-in check. Register the path with onRoutine() / @on_routine() and publish. If the app doesn't have the helper yet, see Apps that need a one-time update above.
A run failed with "Routines don't follow redirects". The path answered by sending the platform somewhere else, for example a trailing-slash or sign-in redirect. Point the routine at the exact path your handler is registered on.
A run failed with a 404. Nothing in the app answers that path. Either the handler isn't registered (check that the file registering it is loaded when the app starts), or the routine's path and the registered path differ. They must match exactly, and letter case counts.
A run failed with a 500. Your handler threw an error. The app's logs have the details.
A run failed with "Your app took longer than … to answer". Give the routine a longer time limit (up to five minutes), or split the work into batches.
A run failed with a 401 saying "This routine call could not be verified". The helper couldn't confirm the call came from the platform. The app's logs say why.
A run was blocked with "This app isn't live". Routines only call apps that are live. Publish the app, then try Run now.
A run was blocked with "This app needs a one-time update before routines can run". The app's code doesn't have the routines helper yet. See Apps that need a one-time update above.
A run was skipped. The previous run was still going, the routine was paused, or the app was asleep. None of these is an error, and the next run happens on schedule.