> ## Documentation Index
> Fetch the complete documentation index at: https://getfloo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Run a scheduled Python job built by an agent

> Turn a Python script your agent wrote into a cron job on floo. It runs on a UTC schedule with DATABASE_URL and your env vars injected, its output lands in floo logs, and you can trigger it by hand.

A `[cron.<name>]` block in `floo.app.toml` runs a command on a UTC schedule inside one of the app's service images. This page deploys an hourly Python job that fetches a JSON document and stores a snapshot in managed Postgres.

Requires a GitHub repository for the job. The job runs in a `web` service's image, so the repository also serves one small HTTP app.

## Set up your agent

```bash theme={null}
curl -fsSL https://getfloo.com/install.sh | bash
floo auth login
```

For Claude Code, also run `floo skills install --path .claude/skills/floo`. Other agents: [Agent setup](https://getfloo.com/docs/guides/agent-setup.md).

## Agent prompt

Paste into your agent. Replace `snapshots` with your app name and the URL with the document to fetch.

```text theme={null}
Deploy this Python script to floo as an hourly cron job with managed Postgres. App name: snapshots.
1. Confirm the floo org with `floo orgs list`. If the org has no plan, have me run `floo billing upgrade`.
2. Keep the job in jobs/snapshot.py. It reads SOURCE_URL and DATABASE_URL from the environment, exits non-zero on failure, and is safe to run twice.
3. Add a minimal FastAPI app at app/main.py with a GET /health route, and a Dockerfile that starts uvicorn on 0.0.0.0:$PORT.
4. Run `floo init snapshots`. In floo.app.toml, set port 8000 on [services.web], add [managed.default] with type = "postgres", and add [cron.snapshot] with schedule "0 * * * *", command "python jobs/snapshot.py", service "web".
5. Run `floo preflight --json` and fix every error.
6. Commit and push to the default branch.
7. Have me open https://github.com/apps/getfloo/installations/new, choose the repository's owner, and select this repository. Wait for me to confirm.
8. Run `floo apps github setup --no-browser` and give me the printed link. Wait for me to confirm GitHub access.
9. Run `floo apps github connect <owner>/<repo> --no-browser`, then `floo deploys status --app snapshots --json` until the deploy is live.
10. Run `floo env set SOURCE_URL=https://example.com/data.json --app snapshots --restart` and wait for that deploy the same way.
11. Run `floo cron run snapshot --app snapshots` and report its result.
```

## floo.app.toml

```toml floo.app.toml theme={null}
[app]
name = "snapshots"

[services.web]
type = "web"
path = "."
port = 8000
ingress = "public"

[managed.default]
type = "postgres"

[cron.snapshot]
schedule = "0 * * * *"
command = "python jobs/snapshot.py"
service = "web"
timeout = 300
```

The job runs in the `web` image with the same `DATABASE_URL` and env vars as the service. `timeout` is in seconds, `1` to `1500`, default `300`. Dev and prod each run their own copy of the job on schedule, against their own database.

## The job

`requirements.txt` lists `fastapi`, `uvicorn`, and `psycopg[binary]`. The Dockerfile is the one in [FastAPI: add a Dockerfile](https://getfloo.com/docs/build/fastapi.md#1-add-a-dockerfile).

```python jobs/snapshot.py theme={null}
import json
import os
import urllib.request

import psycopg
from psycopg.types.json import Jsonb


def main() -> None:
    with urllib.request.urlopen(os.environ["SOURCE_URL"], timeout=30) as resp:
        payload = json.load(resp)
    with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
        conn.execute(
            "CREATE TABLE IF NOT EXISTS snapshots ("
            "taken_at timestamptz NOT NULL DEFAULT now(), payload jsonb NOT NULL)"
        )
        conn.execute("INSERT INTO snapshots (payload) VALUES (%s)", [Jsonb(payload)])
    print("stored snapshot")


if __name__ == "__main__":
    main()
```

```python app/main.py theme={null}
from fastapi import FastAPI

app = FastAPI()


@app.get("/health")
def health() -> dict[str, bool]:
    return {"ok": True}
```

Cron runs do not retry. A run that exits non-zero or passes `timeout` is marked failed, and the next scheduled run is the only recovery.

## CLI commands

The same deploy by hand. Replace `owner/snapshots` with your repository.

```bash theme={null}
floo init snapshots
floo preflight
git add . && git commit -m "feat: hourly snapshot job on floo"
git push origin main
floo apps github setup
floo apps github connect owner/snapshots
floo deploys watch --app snapshots
floo env set SOURCE_URL=https://example.com/data.json --app snapshots --restart
floo deploys watch --app snapshots
```

`--restart` redeploys so the service and the job pick up `SOURCE_URL`.

`floo apps github setup` needs the floo GitHub App installed on the repository's owner first. See [Quickstart steps 2 and 3](https://getfloo.com/docs/introduction.md#2-give-floo-access-to-your-github-repository).

## Verify

```bash theme={null}
floo cron list --app snapshots
floo cron run snapshot --app snapshots
floo logs query --app snapshots --cron snapshot --env dev
floo db query --app snapshots "SELECT count(*) FROM snapshots"
```

`cron list` shows the job and its schedule. `cron run` triggers it now instead of waiting for the hour. The log query prints `stored snapshot` under `[cron:snapshot]`, and the row count goes up by one per run.

A push syncs the dev jobs; a GitHub release syncs the prod jobs. `floo cron list`, `run`, and `logs query --cron` take `--env prod` for prod.

## Next steps

> **[Cron jobs](https://getfloo.com/docs/guides/cron-jobs.md)**
>
> Schedule expressions, fields, failure handling, and JSON output.

> **[Environment variables](https://getfloo.com/docs/guides/environment-variables.md)**
>
> Set secrets from stdin, scope them to services, and import from files.

> **[Postgres](https://getfloo.com/docs/guides/postgres.md)**
>
> Injected variables, compute limits, and keeping the database awake.

> **[Agent webhooks](https://getfloo.com/docs/guides/agent-webhooks.md)**
>
> Send repeated job failures to your agent or on-call.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.