---
question: Can I start a scheduled job now, with my own payload?
kind: task
via_web: available
via_cli: na
via_python: available
via_api: available
via_config: na
ref_web: System › Jobs › Run now
ref_python: trigger_job / get_job
ref_api: POST /api/admin/system/jobs/{module_id}/{job}/trigger
prereq: You need a declared job in a NATS project, and a running module that hosts it. The Web UI and the REST API need a platform super admin account.
next:
  - /capabilities/build/pipelines/monitor-runs
  - /capabilities/build/orchestrations/chain-jobs-with-events
---

# Run a job by hand

Every job can be started on demand, with or without triggers, and with a JSON payload that the handler reads as `ctx.payload`. A manual start is a trigger like a schedule tick: it waits for a free slot under `max_concurrency`, is retried under `max_attempts`, and is recorded like any run, tagged **Manual**. A job with no trigger at all runs only this way.

## Web UI

### Open the job

Open **System** from the Nexus sidebar, then the **Jobs** tab. It lists the jobs of the engine and of every registered module. Click the job.

### Run it

Click **Run now**. The dialog shows the module id and an editor for the JSON payload; leave `{}` when the job needs none. Click **Run** (or press ⌘↵). The start is written to the audit log.

### Follow the run

The new run shows in the job's **Runs**, `QUEUED` until a replica picks it up. Click it: its panel has the **Logs**, **Payload**, **Result** and **Trace** tabs, **Cancel** while it runs, and **Re-run**, which opens the same dialog with that run's payload. See [Monitor runs](/capabilities/build/pipelines/monitor-runs?via=web).

## CLI

Not applicable. No `abi` command starts a job. Use the Web UI, or call the REST API from a script.

## Python

### Start one of the module's own jobs

From async code in the module, such as an agent tool or another job:

```python
run = await self.atrigger_job(
    "export_report",
    {"day": "2026-10-25"},
    idempotency_key="export-2026-10-25",
)
record = await run.wait(timeout=600)  # the run record, once it ends
print(record["status"], record.get("result"))
```

From synchronous code, such as a request handler of an in-engine module, call `self.trigger_job(...)` with the same arguments; it blocks until the trigger is stored. Both raise `JobsNotHosted` until the module has started, and always when NATS is off.

The returned `JobRun` has `run_id`, `await run.status()` (`{"status": "QUEUED"}` until a replica picks it up), `await run.wait(timeout=...)` and `await run.cancel()`, which asks the running replica to set `ctx.cancelled`.

### Start another module's job

A remote module names the other module in its dependencies, then resolves the job through discovery:

```python title="src/notices/__init__.py"
from naas_abi_sdk import BaseModule, ModuleDependencies


class ABIModule(BaseModule):
    dependencies = ModuleDependencies(modules=("reports",))

    async def rebuild(self, day: str) -> str:
        export = await self.engine.modules["reports"].get_job("export_report")
        run = await export.trigger({"day": day}, idempotency_key=f"export-{day}")
        return run.run_id
```

`get_job` asks discovery first. It raises `RPCError` with code `MODULE_UNAVAILABLE` when that module has no ready replica (nothing is stored: retry later), and `JOB_NOT_FOUND` when the first ready replica does not declare the job.

Code outside a module, or a job of the engine itself (engine modules are not in discovery), can use an `ABIClient`: `client.get_job("<module id>", "export_report").trigger(...)`. It checks nothing before the trigger: a run for a job nobody hosts stays `QUEUED`.

## REST API

The System routes of the Nexus API. Auth: a Nexus session of a **platform super admin** (`POST /api/auth/login` returns the access token); anyone else gets 403. Every start is written to the audit log first.

### Trigger the job

The path takes the module id (shown under the job name in System › Jobs) and the job name. The body is optional:

```bash
curl -X POST "http://localhost:9879/api/admin/system/jobs/reports/export_report/trigger" \
  -H "Authorization: Bearer $NEXUS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"payload": {"day": "2026-10-25"}}'
```

The answer is `202 Accepted` with the run's id: `{"run_id": "export_report:42", "key": "reports/export_report:42"}`. An unknown module or job answers 404; an audit log that cannot be written answers 503, and nothing is started.

With `abi dev up`, use the `api` port that `abi dev ports` prints.

### Read the run

`GET /api/admin/system/jobs/runs` lists runs across jobs. See [Monitor runs](/capabilities/build/pipelines/monitor-runs?via=api).

## Config

Not applicable. Nothing in `config.yaml` starts a job.

## Idempotency keys

A retry of a manual start, after a timeout or a crash on the caller's side, can start the same work twice. An `idempotency_key` prevents that: a second trigger with the same key for the same job **within two minutes** stores nothing and returns the first run, with `run.duplicate` set to `True`. Build the key from what makes the work unique (`export-2026-10-25`), not from the time of the call.

Only Python takes a key. **Run now** and the REST route do not: each call starts a new run. Past two minutes the same key starts a new run too, so the job itself should still be [safe to run twice](/capabilities/build/orchestrations/chain-jobs-with-events#make-each-step-safe-to-run-twice).
