API REFERENCE

The Scheduled Tasks API

The endpoints behind the My-tasks panel. Users create tasks from chat, and these routes let your app read, mark seen, pause, or cancel them from your own UI.

A scheduled task is work the agent runs later, on its own. One-off tasks fire once at an ISO timestamp, recurring tasks fire on a 5-field cron in a named IANA timezone. Each run happens unattended as the owning user, and a successful result surfaces on their next visit. These endpoints expose the same list the widget's My-tasks panel shows.

Scheduling is a signed-mode feature. Every call needs a signed user_context, or the request is rejected with SIGNED_IDENTITY_REQUIRED. GET carries it in the X-Era-User header (URL-encoded), POST and PATCH carry it in the JSON body, and DELETE passes it as a user_context query param.
These routes are origin-checked even server-to-server. If your project has an allowed-origins list, a request with no Origin header is rejected with 403 ORIGIN_NOT_ALLOWED — including a plain backend curl. Send an Origin header matching one of your allowed origins (a trailing slash on the header is stripped before comparison), or leave the allowlist empty. Every example below sends one.
Scheduled tasks are PostgreSQL-only. Without DATABASE_URL the task tables do not exist, so these routes return an empty list and a 404 on anything addressed by id.

The task object

Every endpoint returns tasks in this shape:

FieldTypeDescription
task_idstringTask identifier, "sch_" followed by 12 hex characters. Pass it to update and cancel.
instructionstringThe instruction the agent runs at fire time.
schedule_kind"once" | "cron"Whether the task fires once or repeats on a cron.
run_atstring (ISO 8601) | nullFor a once task, when it fires. Null for cron.
cronstringFor a cron task, a 5-field cron expression. Empty for once.
timezonestring (IANA)The zone the schedule is evaluated in, e.g. "Asia/Riyadh". Defaults to "UTC" when the task was created without one.
statusstringOne of the statuses below.
next_run_atstring (ISO 8601) | nullThe next scheduled fire time in UTC, computed from cron + timezone. Null once the task is done.
last_run_atstring (ISO 8601) | nullWhen the task last fired. Null before the first run.
run_countintAttempts that reached the agent, successful or not. A run that errored or timed out is counted; a run skipped for budget is not.
max_runsintHow many attempts a task may make before it is marked done. 0 means unlimited.
last_resultstring | nullThe most recent run's output, truncated to 1000 characters. Null when there is no output, which is the case for every run that did not finish ok.
last_run_statusstring | nullStatus of the most recent run: "ok", "error", "skipped_budget", or "cancelled". Null before the first run.
last_errorstring | nullWhy the most recent run did not succeed, truncated to 500 characters. Null when it did. A failed run has no output, so this is the only thing that explains it.

Status values:

FieldTypeDescription
activestatusWaiting for its next fire time.
pausedstatusPaused by the user or your app. Resume by setting status back to "active".
runningstatusCurrently executing a run. A task left here by a backend restart is reset to active on the next startup.
donestatusA once task that has fired, or a cron task that reached max_runs. Never fires again.
cancelledstatusCancelled for good. You will not see this value here — the list route filters cancelled tasks out entirely. Only the console's admin view shows them.
Tasks are created from inside chat, not from these endpoints. The agent calls the reserved tools schedule_task, list_scheduled_tasks, update_scheduled_task, and cancel_scheduled_task. These HTTP routes are for reading and managing what already exists.

List tasks

GET/v1/scheduled-tasks

Returns the tasks owned by the user in the signed identity, newest first, plus a top-level unseen count. Cancelled tasks are excluded. Identity is carried in the X-Era-User header.

bash
curl https://agentifys.ai/v1/scheduled-tasks \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Origin: https://app.example.com" \
  -H "X-Era-User: %7B%22id%22%3A%22u_42%22%2C%22_ts%22%3A1735689600%2C%22_sig%22%3A%22a1b2c3...%22%7D"
json
{
  "tasks": [
    {
      "task_id": "sch_7a1c9e40b2d3",
      "instruction": "Summarize this week's open tickets and email me.",
      "schedule_kind": "cron",
      "cron": "0 9 * * 1",
      "run_at": null,
      "timezone": "Asia/Riyadh",
      "status": "active",
      "next_run_at": "2026-09-07T06:00:00Z",
      "last_run_at": "2026-08-31T06:00:00Z",
      "run_count": 3,
      "max_runs": 0,
      "last_result": "6 tickets open, summary sent.",
      "last_run_status": "ok",
      "last_error": null
    }
  ],
  "unseen": 1
}
Only the latest run is exposed. There is no public route for run history: each task carries one last_result, truncated to 1000 characters, and it is overwritten on the next fire. Full history — up to 50 runs with tokens, tools used and untruncated output — exists only in the console. If your product needs to show a user their own history, store each result as you read it.
FieldTypeDescription
unseenintRun results the user has not acknowledged. Counts successful runs only: a run that errored, timed out or was skipped for budget never raises this number, even though it appears on the task.

Mark results seen

POST/v1/scheduled-tasks/seen

Acknowledges the run results so the My-tasks panel can clear its unread badge. This marks all of the user's undelivered runs seen in one call and drives the unseen count back to zero. It does not create tasks.

FieldTypeDescription
user_contextrequiredobjectThe signed identity whose runs are being acknowledged.
bash
curl -X POST https://agentifys.ai/v1/scheduled-tasks/seen \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Origin: https://app.example.com" \
  -H "Content-Type: application/json" \
  -d '{ "user_context": { "id": "u_42", "_ts": 1735689600, "_sig": "..." } }'

Response, with the number of run records just marked delivered:

json
{ "ok": true, "marked": 1 }
marked can exceed the unseen you just read. The count marks every undelivered run regardless of status, while unseen counts only successful ones. A live chat turn also marks results seen on its own: up to five unseen results are handed to the agent at the start of the turn and acknowledged at that moment, whether or not it mentions them.

Pause or resume a task

PATCH/v1/scheduled-tasks/{task_id}

Pauses or resumes a task. The task id goes in the path, and the body carries the new status. This is the only edit these routes support: retiming a task, or changing its cron, timezone, or max_runs, is not exposed here. Set status to "paused" to stop it firing, or "active" to resume it.

FieldTypeDescription
user_contextrequiredobjectThe signed identity that owns the task.
statusrequired"active" | "paused"Set "paused" to pause, or "active" to resume.
bash
curl -X PATCH https://agentifys.ai/v1/scheduled-tasks/sch_7a1c9e40b2d3 \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Origin: https://app.example.com" \
  -H "Content-Type: application/json" \
  -d '{ "user_context": { "id": "u_42", "_ts": 1735689600, "_sig": "..." }, "status": "paused" }'
json
{ "ok": true, "status": "paused" }
Pausing does not free a slot. A user's task cap counts active, paused and running tasks alike, so a paused task still blocks them from creating a new one. Cancel it if you want the slot back.

Cancel a task

DELETE/v1/scheduled-tasks/{task_id}

Cancels a task for good. The task id goes in the path and the signed identity is passed as a user_context query param (URL-encoded JSON). A cancelled task never fires again, and it disappears from GET /v1/scheduled-tasks rather than reappearing with status cancelled — so treat it vanishing from the list as your confirmation, not a status change you can poll for.

bash
curl -X DELETE "https://agentifys.ai/v1/scheduled-tasks/sch_7a1c9e40b2d3?user_context=%7B%22id%22%3A%22u_42%22%2C%22_ts%22%3A1735689600%2C%22_sig%22%3A%22...%22%7D" \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Origin: https://app.example.com"
json
{ "ok": true }

A task id that does not exist, or that belongs to another user, returns 404 NOT_FOUND. The same is true of PATCH.

How a run ends

Every attempt writes a run record, and the task carries the latest one in last_run_status and last_error. These are the outcomes your UI has to render.

FieldTypeDescription
oklast_run_statusThe agent turn completed. last_result holds the output and unseen goes up by one.
errorlast_run_statusThe run raised, or passed the 120-second wall clock — last_error is "run timed out". run_count still increments, and a cron task advances to its next slot.
skipped_budgetlast_run_statusThe project hit its monthly token budget before the run started. Nothing was spent or executed, and run_count does not increment.
cancelledlast_run_statusThe owning user no longer exists in the project. The task is cancelled, which also removes it from this list route.
blockeddefinedReserved for a run stopped at an unauthorised write. The current engine removes unauthorised write tools before the model sees them, so no run is recorded with this status today.
A one-off task skipped for budget never runs. The budget gate advances the task exactly as a finished attempt would, and for schedule_kind: "once" that means status done with next_run_at: null. Raising the budget afterwards does not bring it back; the task has to be created again. A cron task survives the same gate and simply tries at its next slot.
A failed run still spends a run against max_runs. run_count increments after every attempt that reached the agent, including one that errored or timed out, so a task capped at 5 that fails five times is marked done having produced nothing. Only budget skips are exempt. If you set a cap, read last_run_status before you trust run_count as a measure of work done.
A scheduled run only performs write actions pre-authorised for that task, drawn from the project's writable-tools list, which starts empty. In the default configuration a run is handed no write tools at all: it does not fail, it reports what it could not do and finishes ok. The missing write is visible only in the text of last_result.