Controlling a task from a script
Every run gets its own context. The API address is shared; the task and run IDs are determined automatically. One file can serve several tasks. There is no need to look a task up by name or read its ID from the database.
Python
The z3ndash module ships with the application. The scheduler adds it to PYTHONPATH
automatically, including runs inside a venv. Installing the package with pip is not
needed.
from z3ndash import current_task
try:
do_work()
except ServiceUnavailable:
current_task.defer(seconds=3600, reason="Service unavailable")
raise
defer() does not end the script and does not swallow errors. Once the deferral is
confirmed, the script decides for itself whether to finish, release resources or raise.
If the API is unreachable the helper raises: a deferral must not be assumed to be set.
state = current_task.get() # task_id, run_id, name and the schedule state
current_task.pause() # Pause automatic runs indefinitely
current_task.resume() # Lift the pause and any temporary deferral
current_task.defer(until="2026-12-01T12:00:00Z", reason="Until a given time")
C# and XML OwnCode
In csx-internal and in XML OwnCode blocks the current_task object is available:
await current_task.DeferAsync(3600, "Service unavailable");
var state = await current_task.GetAsync();
In a synchronous block: current_task.DeferAsync(3600).GetAwaiter().GetResult();.
Inside the application's own handlers the same context is available as
TaskRunContext.Current. The context is isolated between parallel runs.
HTTP API
External processes receive Z3NDASH_API_URL, Z3NDASH_RUN_TOKEN, Z3NDASH_TASK_ID
and Z3NDASH_RUN_ID through the environment. The address carries the actual port of the
running application. For /self the address and the token are enough.
Every request carries the Authorization: Bearer <Z3NDASH_RUN_TOKEN> header.
The token is valid only until the run ends. Do not print it to logs.
| Method | Path | Body |
|---|---|---|
| GET | /api/v1/self |
— |
| POST | /api/v1/self/defer |
{"delay_seconds":3600,"reason":"Service error"} |
| POST | /api/v1/self/defer |
{"until":"2026-12-01T12:00:00Z"} |
| POST | /api/v1/self/pause |
{} |
| POST | /api/v1/self/resume |
{} |
Every command answers with the state: task_id, run_id, name, enabled, paused,
deferred_until (UTC or null), reason, schedule_mode. The token is not part of the
answer. delay_seconds is a positive number of seconds from the moment of the request.
until is a future time with Z or a UTC offset. The two fields cannot be sent together.
A reason is optional, 2000 characters at most. Errors: 400 — bad parameters,
401 — missing or finished context, 404 — task or command not found,
503 — database not connected, 500 — internal error. The error body is {"error":"..."}.
A Node.js example:
const response = await fetch(process.env.Z3NDASH_API_URL + '/api/v1/self/defer', {
method: 'POST',
headers: { 'Content-Type': 'application/json',
Authorization: 'Bearer ' + process.env.Z3NDASH_RUN_TOKEN },
body: JSON.stringify({ delay_seconds: 3600, reason: 'Service unavailable' })
});
if (!response.ok) throw new Error(await response.text());
How the schedule behaves
- A deferral applies to the whole task. Instances already admitted keep running; new automatic runs and the queue wait.
- Reserved instances waiting on a start delay check the pause as well.
- Deferrals and indefinite pauses are stored in the database and survive a restart.
- Competing deferrals pick the latest time. Shortening it takes an explicit
resumefollowed by a newdefer. - Once a deferral expires the previous schedule and its limits apply again. It is a ban on starting before the given time, not a promise to start exactly at that second. The scheduler checks readiness once a minute.
- A manual
Runstarts the task at once and does not lift the deferral. A manual run that lands in the queue because threads are busy waits along with the queue. resumelifts the pause and the deferral but changes neitherenablednor the schedule mode. A disabled task has to be enabled separately.- Tasker shows the deferral deadline and a
Resume schedulebutton to lift it.
Driving the application from outside
The browser interface API works at the same address:
GET /tasker/list, POST /tasker/save, /tasker/run, /tasker/stop,
/tasker/delete, GET /tasker/instances?id=..., /tasker/queue?id=....
The run, stop and delete commands take the JSON {"id":"task ID"}.
save accepts an id and the task fields to change, for example enabled,
schedule_mode, cron, schedule_json. Without an id it creates a new task.
There are also operator commands: POST /tasker/pause and /tasker/resume with the
body {"id":"..."}, and POST /tasker/defer?id=... with the same body as
/self/defer. They use the existing access to the dashboard API.
To control its own task from a script, use /self: no ID is needed there.