---
title: Scheduled jobs
description: A scheduled job makes one HTTP call to one of your organization's apps on a clock, like Google Cloud Scheduler.
url: https://agentlab.cresclab.com/docs/en/deploy/scheduled-jobs
language: en
updated: 2026-10-06
---

# Scheduled jobs

A scheduled job makes one HTTP call to one of your organization's apps on a clock, like Google Cloud Scheduler.

## What a job is made of

The call reaches whichever version is live at that moment.

| Field     | Description                                                                                                       |
| --------- | ----------------------------------------------------------------------------------------------------------------- |
| App       | The container app to call.                                                                                        |
| Path      | Appended to the app's address. Starts with `/`; a query string is allowed. For example `/tasks/daily`.            |
| Method    | `GET`, `POST`, `PUT`, `PATCH` or `DELETE`; `POST` by default. A body is sent only with `POST`, `PUT` and `PATCH`. |
| Schedule  | A five-field cron expression: minute, hour, day of month, month, day of week. At most once a minute.              |
| Time zone | `Asia/Taipei` by default.                                                                                         |

Some cron expressions:

| Expression     | Runs                               |
| -------------- | ---------------------------------- |
| `0 9 * * *`    | Every day at 09:00                 |
| `*/15 * * * *` | Every 15 minutes                   |
| `0 8 * * 1`    | Every Monday at 08:00              |
| `0 0 1 * *`    | At 00:00 on the 1st of every month |

## Create a job

1. **Add an endpoint to your app** For example `POST /tasks/daily`, with the recurring work in it. Deploy it and go live.
2. **Create the job** Open **Deploy › Scheduled jobs**, choose the app, and enter the path and schedule.
3. **Run it once by hand** Use **Run now**, then check the response code and log in the run history.

## Check that a call came from the schedule

By default a call carries no proof of where it came from. Choose one of these:

- Set your own header on the job, such as `X-Cron-Secret`, store the value as a secret of the app, and compare the two in your code.
- Turn on the signed token. The call then carries `Authorization: Bearer <JWT>`; verify it against `https://api.agentlab.cresclab.com/.well-known/jwks.json` with `aud` = `franky:app:<app ID>`.

## How runs behave

| Item     | Behavior                                                                                   |
| -------- | ------------------------------------------------------------------------------------------ |
| Overlap  | Runs never overlap. A run that comes due while the previous one is still going is skipped. |
| Delivery | At least once, so a run can arrive twice. De-duplicate on the `X-Franky-Run-Id` header.    |
| Retries  | 0 by default, at most 5.                                                                   |
| Deadline | 15 seconds to 30 minutes; 3 minutes by default.                                            |

```js server.js
const runId = req.headers["x-franky-run-id"];
if (await alreadyHandled(runId)) {
  res.writeHead(200).end();
  return;
}
```
