9Login API: tasks#
Create, change, run and cancel tasks, and read the result of every profile in a run.
GET /v1/tasks#
The scheduled tasks of the account, with their next and last run on this computer. A task runs a script on a set of profiles on a schedule, on every computer where 9Login is open and signed in to the account. Answers from the list as this computer has it, sorted by title. state.nextRunAt is when the schedule fires next on this computer (null for manual tasks and tasks that are off); state.running says whether a run is going on right now. Times are Unix milliseconds.
MCP tool: list_tasks. Access: read only.
| Field | Type | Required | Description |
|---|---|---|---|
search |
string |
no | Only tasks whose title contains this, case-insensitive. |
refresh |
boolean |
no | Fetch the account's tasks first, to see ones saved on another device. Do not poll with it: the server limits how often an account reads its lists, for this app too. Default false. |
Response
{
"ok": true,
"data": {
"total": 1,
"refreshed": false,
"refreshError": null,
"items": [
{
"id": "0A1B2C3D4E5F6G7H",
"title": "Warm up shop accounts",
"enabled": true,
"script": {
"kind": "library",
"scriptId": "0S1C2R3I4P5T6A7B"
},
"params": {
"minutes": 10
},
"profileIds": [
"k3m9q2x7v4b8n6c1",
"p9w2e5r8t1y4u7i0"
],
"schedule": {
"kind": "daily",
"at": "09:00",
"days": [
1,
2,
3,
4,
5
]
},
"run": {
"maxParallel": 3,
"staggerMin": 20,
"staggerMax": 90
},
"createdAt": 1760000000000,
"state": {
"nextRunAt": 1760086800000,
"lastRunAt": 1760000400000,
"running": false,
"runId": null
}
}
]
}
}
GET /v1/tasks/{id}#
One task with its whole configuration, schedule state and latest run.
MCP tool: get_task. Access: read only.
| Field | Type | Required | Description |
|---|---|---|---|
refresh |
boolean |
no | Fetch the account's tasks first, to see ones saved on another device. Do not poll with it: the server limits how often an account reads its lists, for this app too. Default false. |
Request
Response
{
"ok": true,
"data": {
"id": "0A1B2C3D4E5F6G7H",
"title": "Warm up shop accounts",
"enabled": true,
"script": {
"kind": "library",
"scriptId": "0S1C2R3I4P5T6A7B"
},
"params": {
"minutes": 10
},
"profileIds": [
"k3m9q2x7v4b8n6c1",
"p9w2e5r8t1y4u7i0"
],
"schedule": {
"kind": "daily",
"at": "09:00",
"days": [
1,
2,
3,
4,
5
]
},
"run": {
"maxParallel": 3,
"staggerMin": 20,
"staggerMax": 90
},
"createdAt": 1760000000000,
"state": {
"nextRunAt": 1760086800000,
"lastRunAt": 1760000400000,
"running": false,
"runId": null
},
"lastRun": {
"id": "7R3M9K2P4S6T8V0X",
"taskId": "0A1B2C3D4E5F6G7H",
"taskTitle": "Warm up shop accounts",
"source": "api",
"status": "done",
"startedAt": 1760000400000,
"endedAt": 1760001300000,
"total": 12,
"ok": 10,
"failed": 1,
"skipped": 1,
"cancelled": 0
}
}
}
POST /v1/tasks/save#
Create a task or change one, as Save in the Tasks screen does. Leave out id to create a task: title and script are required, everything else takes the defaults of a new task (manual schedule, no profiles, 3 profiles at once, 20 to 90 seconds between launches). To change a task, pass its id with the fields to change; a field left out keeps its value, and script, profileIds and schedule replace the whole value. The schedule fires on every computer signed in to the account while 9Login is open there; a profile another computer opened first is skipped with lockedElsewhere. The account stores all its tasks as one list, so a change made on another computer first answers conflict: call again with onConflict "reload" to take that version and apply this change on top of it. A task never closes the browsers it opens: when the script should close its browser, end it with Exit().
MCP tool: save_task. Access: needs "Allow changes", needs the sub-user permission "manageScript", needs a plan with scripts.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
no | Task id of the task to change. Leave out to create a task. |
title |
string |
no | Required to create. At most 200 characters. |
enabled |
boolean |
no | Off: the schedule does not fire; run_task still works. New tasks are on. |
script |
object |
no | Which script the task runs. |
script.kind |
string |
yes | "library": one script for every profile (scriptId). "profile": the script each profile has set (scriptId on the profile); profiles without one are skipped. Values: library, profile. |
script.scriptId |
string |
no | Script id from GET /v1/scripts, for kind "library". |
params |
string |
no | A JSON object as text, handed to the script as TASK.params. "{}" for none. |
profileIds |
string[] |
no | The profiles the task runs on, in this order, as GET /v1/profiles lists them; at most 2000. Replaces the whole list. |
schedule |
object |
no | When the task fires, at the exact time (no random offset). Clock times are this computer's local time. A firing missed while 9Login was closed is skipped; a firing while the previous run is still going is skipped too. |
schedule.kind |
string |
yes | "manual": only run_task fires it. "once": at a time (at). "interval": every everyMin minutes, around the clock. "daily": at a clock time (at) on days. Values: manual, once, interval, daily. |
schedule.at |
string |
no | For "once": Unix milliseconds as a number string, or an ISO date-time, in the future. For "daily": HH:MM. |
schedule.everyMin |
integer |
no | For "interval": minutes between runs. Min 1, max 10080. |
schedule.days |
integer[] |
no | Weekdays, 0 = Sunday to 6 = Saturday. Empty means every day. |
run |
object |
no | How a run opens the profiles. Fields left out keep their value. The task only opens each profile and runs the script in it: it does not close the browser, set a time limit or retry; the profile's own settings and Exit() in the script do that. |
run.maxParallel |
integer |
no | Profiles running the script at the same time. Min 1, max 20. |
run.staggerMin |
integer |
no | Smallest random gap between two launches, seconds. Min 0, max 3600. |
run.staggerMax |
integer |
no | Largest random gap between two launches, seconds. Min 0, max 3600. |
onConflict |
string |
no | When the task list was changed on another computer first: "fail" answers conflict and changes nothing; "reload" takes that version and applies this change on top of it. Values: reload, fail. Default "fail". |
Request
curl -X POST "http://127.0.0.1:37286/v1/tasks/save" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Warm up shop accounts",
"script": {
"kind": "library",
"scriptId": "0S1C2R3I4P5T6A7B"
},
"profileIds": [
"k3m9q2x7v4b8n6c1",
"p9w2e5r8t1y4u7i0"
],
"schedule": {
"kind": "daily",
"at": "09:00",
"days": [
1,
2,
3,
4,
5
]
}
}'
Response
{
"ok": true,
"data": {
"id": "0A1B2C3D4E5F6G7H",
"title": "Warm up shop accounts",
"enabled": true,
"script": {
"kind": "library",
"scriptId": "0S1C2R3I4P5T6A7B"
},
"params": {
"minutes": 10
},
"profileIds": [
"k3m9q2x7v4b8n6c1",
"p9w2e5r8t1y4u7i0"
],
"schedule": {
"kind": "daily",
"at": "09:00",
"days": [
1,
2,
3,
4,
5
]
},
"run": {
"maxParallel": 3,
"staggerMin": 20,
"staggerMax": 90
},
"createdAt": 1760000000000,
"state": {
"nextRunAt": 1760086800000,
"lastRunAt": 1760000400000,
"running": false,
"runId": null
},
"created": true,
"reloaded": false
}
}
POST /v1/tasks/delete#
Delete tasks from the account, with their run history on this computer. Needs "Allow deleting" on the API & MCP screen. There is no undo. A run still going is stopped first. An id this computer does not know is listed in failed as notFound; list with refresh true first to see tasks saved on another device. The task list is one list in the account, so a change made on another computer first answers conflict: call again with onConflict "reload".
MCP tool: delete_tasks. Access: needs "Allow deleting", needs the sub-user permission "manageScript".
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] |
yes | Task ids, at most 200. |
onConflict |
string |
no | When the task list was changed on another computer first: "fail" answers conflict and changes nothing; "reload" takes that version and applies this change on top of it. Values: reload, fail. Default "fail". |
Request
Response
POST /v1/tasks/run#
Start a run of a task right now, outside its schedule. Runs the script in each of the task's profiles, one by one with the gaps the task sets, and records a run. A profile that is already open gets the script in its open browser; one that is not open is opened first. The task never closes a browser: when the script finishes, the browser stays open as the script left it, and the next firing runs the script again in that open browser. A script that should close its browser ends with Exit(); the profile's Duration setting also closes it after a set time. The answer is the run; by default it waits up to waitSec for the run to finish, and a run still going answers with status "running": follow it with GET /v1/task-runs/{id}. A task with a run already going answers busy; a plan without scripts answers planRequired.
MCP tool: run_task. Access: needs "Allow changes", needs a plan with scripts.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
yes | Task id. |
wait |
boolean |
no | Wait for the run to finish, at most waitSec, before answering. Default true. |
waitSec |
integer |
no | How long wait may hold the request. Min 1, max 100. Default 60. |
Request
Response
POST /v1/tasks/cancel#
Stop a run of a task that is still going. Profiles not opened yet are marked cancelled; every browser still running the script is closed, including browsers that were open before the run started. Finished profiles keep their result. A run that already ended answers 200 with cancelled false.
MCP tool: cancel_task_run. Access: needs "Allow changes".
| Field | Type | Required | Description |
|---|---|---|---|
runId |
string |
yes | Run id, from run_task or the task's state.runId. |
Request
Response
GET /v1/tasks/{id}/runs#
The runs of a task recorded on this computer, newest first. This computer keeps the last 50 runs of each task. Runs made on another computer are not here.
MCP tool: list_task_runs. Access: read only.
| Field | Type | Required | Description |
|---|---|---|---|
page |
integer |
no | Page number, from 1. Min 1. Default 1. |
limit |
integer |
no | Runs per page. Min 1, max 50. Default 20. |
Request
Response
{
"ok": true,
"data": {
"total": 1,
"page": 1,
"limit": 20,
"items": [
{
"id": "7R3M9K2P4S6T8V0X",
"taskId": "0A1B2C3D4E5F6G7H",
"taskTitle": "Warm up shop accounts",
"source": "api",
"status": "done",
"startedAt": 1760000400000,
"endedAt": 1760001300000,
"total": 12,
"ok": 10,
"failed": 1,
"skipped": 1,
"cancelled": 0
}
]
}
}
GET /v1/task-runs/{id}#
One run with the result of every profile in it. profiles lists each profile of the run: status ok, failed, launchFailed, skipped or cancelled; value is what the script returned (JSON text); error says why it failed or was skipped (for skipped: busy, profileRunning, noChannel, notFound, inSync, dataBusy, noScript, planRequired, lockedElsewhere, refused). With logs true each profile also carries what the browser logged with Log() during this run, as a text that may be large.
MCP tool: get_task_run. Access: read only.
| Field | Type | Required | Description |
|---|---|---|---|
logs |
boolean |
no | Include the Log() output of each profile. Default false. |
Request
Response
{
"ok": true,
"data": {
"id": "7R3M9K2P4S6T8V0X",
"taskId": "0A1B2C3D4E5F6G7H",
"taskTitle": "Warm up shop accounts",
"source": "api",
"status": "done",
"startedAt": 1760000400000,
"endedAt": 1760001300000,
"total": 12,
"ok": 10,
"failed": 1,
"skipped": 1,
"cancelled": 0,
"profiles": [
{
"profileId": "a1b2c3",
"profileTitle": "shop 1",
"status": "ok",
"startedAt": 1760000400000,
"endedAt": 1760000520000,
"value": "\"done\"",
"error": "",
"exitCode": null
},
{
"profileId": "d4e5f6",
"profileTitle": "shop 2",
"status": "skipped",
"startedAt": 1760000400000,
"endedAt": 1760000400000,
"value": "",
"error": "profileRunning",
"exitCode": null
}
]
}
}