Runs
Every time a job is triggered, novem records a run. Each run keeps its own log, output, stats and status, reachable under the job's runs directory.
AI assisted, human approved — novem uses AI to review and keep our documentation up to date.
A run is one execution of a job's chain,
whether you triggered it by hand, a schedule
fired, or an inbound e-mail kicked it off. Runs live under the job's runs
directory:
daily_report
├── data => POST here to trigger a run
├── log => Latest run's log (shortcut)
├── stats
│ └── runs => Run history (time, trigger, duration, status)
└── runs
└── <run> => One entry per run
├── log => The run's log
├── output => The run's result file(s)
├── stats => Run metadata (timings, trigger, …)
└── status => processing / success / failed
Trigger a run by posting to the job's data endpoint (this is what the CLI's
-R flag does). What you send becomes the run's input, mounted at /input
inside the first chain step:
- A JSON body (
Content-Type: application/json) is stored as/input/input.json. The body must be a JSON document; an empty or non-JSON body is rejected with400. - A
multipart/form-dataupload stores each file as/input/<filename>, original names preserved.
# trigger a run (input files each prefixed with @)
novem -j daily_report -R
novem -j daily_report -R @data.csv
# save the run's output to disk
novem -j daily_report -R -o ./outThe request stays open while the chain executes, and the response body is the
run's result: a single output file is returned as-is, several are bundled
into one .zip. See how your code runs for the
/input / /output contract.
Triggering requires write access to the job; reading runs requires read access.
GET /v1/code/jobs/<job>/runs returns the 30 most recent runs as a JSON directory listing, newest
first. Each entry's name is the run id, <YYYYMMDD>-<HHMMSS>-<request id>,
with created_on set to when the run started and last_modified to when
it completed (or started, while still running).
[
{
"name": "20260613-081502-f3a9c2d4...",
"uri": "/v1/code/jobs/daily_report/runs/20260613-081502-f3a9c2d4...",
"type": "dir",
"permissions": ["r"],
"actions": ["OPTIONS", "GET"],
"created_on": "2026-06-13T08:15:02Z",
"last_modified": "2026-06-13T08:15:31Z"
}
]
GET .../runs/<run> lists the four per-run files described below.
| Endpoint | Returns |
|---|---|
runs/<run>/status | The run's state as plain text |
runs/<run>/stats | Run metadata — timings, trigger, who started it |
runs/<run>/log | The run's timestamped log |
runs/<run>/output | The run's result file(s) |
A single plain-text value: processing while the chain executes, then
success or failed. A run that never started because a permission check
blocked it shows rejected.
Run metadata. Plain text key: value pairs by default; send
Accept: application/json for a JSON object:
| Field | Description |
|---|---|
name | The run id |
shortname | The run's auto-generated shortname |
status | Same value as the status endpoint |
origin / origin_shortname | The job the run belongs to |
started_on / completed_on | UTC timestamps; completed_on is null while running |
duration | Seconds, two decimals; null while running |
trigger | api, email or schedule |
source | The client that triggered it (cli, webpage, scheduler, …) |
has_output | Whether output has anything to download |
created_by | Username of whoever triggered the run |
The run's log entries in chronological order. Plain text by default. With
Accept: application/json you get an array of
{ "log_time", "severity", "message" } objects with UTC ISO timestamps. A
run with no log entries returns an empty 200.
Downloads the run's result with its original filename and content type, the
same payload the triggering request received. A run that produced no output
returns 204 No Content.
Run logs and output are retained for 30 days. After that, log and
output answer 410 Gone; the run listing, stats and status remain
available indefinitely.
Two conveniences live directly on the job:
| Endpoint | Returns |
|---|---|
GET .../jobs/<job>/log | The latest run's log — same formats as a run's log |
GET .../jobs/<job>/stats/runs | The full run history: one row per run with time, trigger, duration and status (plain-text table, or JSON with Accept: application/json) |
stats/runs is not capped at 30 entries, so it's the place to look when the
runs listing has rotated past what you're after.
Note: Runs are never public. Even when a job is shared with public,
its runs, logs and output are only readable by authenticated users with
read access to the job: the owner and explicit share grantees.
- Jobs overview — the
/input//outputcontract. - Schedule — trigger runs on a cron schedule.
- Config — every job configuration key.