API
Everything the pages do calls /api, JSON over the session cookie, or over a token in an Authorization: Bearer header (task-ui token <user> <name> prints one). Writes need the same origin. The browser pages are the reference client; scripts/run.sh is the curl one.
The surface below, and the MCP tools on it, stay frozen: a change adds a route or a tool only for a need these cannot serve, and states the reason.
| Added | For |
|---|---|
DELETE /api/artifacts |
Delete all on the Artifacts page: deleting the records one by one leaves the files and their history |
/api/runs/{id}/summary |
Summary on the Runs page: Claude’s summary of a run, written once and kept |
/api/workflow-runs/{id}/summary |
Summary on a workflow’s runs: Claude’s summary of every step, written once and kept |
POST /api/users |
Add user on the Users page: the password file had no writer but task-ui passwd on the server |
/api/artifacts/files/{path} |
The preview window: a file of the tree, rendered, with its versions, and the delete of one file, which the records cannot reach |
/api/notes, list_notes, note_comment |
Notes, and instructions to the agent, which reads and reports on them over MCP |
/api/integrations, /api/inventory |
Settings, Integrations, which keeps the services’ tokens encrypted, the inventory from Beszel, and commands run on its hosts |
/api/workspaces, workspace on POST /api/projects |
Workspaces: a directory whose folder is the namespace of the projects cloned inside it, a resource the project routes cannot express. Any number of workspaces may clone one repository, and deleting one takes its projects with it |
POST /api/workspaces/{folder}/prompt |
Prompt on a workspace’s folder page: Claude Code on a prompt in the workspace’s home, a run of @claude-prompt whose environment, TASK_UI_PROMPT and the workspace, POST /api/runs cannot carry |
GET, POST /api/pins, DELETE /api/pins/{id} |
Pin and Unpin on a folder’s page and a job’s row: what a user adds to the sidebar, a folder, a directory or a job with its directory and args, the user’s own. No route kept anything per user for the pages to draw. Schema pin adds the table |
sort on PUT /api/workspaces/{folder} |
Move up and Move down on the Workspaces page: the order of the workspaces in the sidebar, which no field held |
/api/workspaces/{folder}/keys, /keys/generate, /keys/upload, /keys/{name} |
Keys on a workspace’s settings page: the SSH keys of the workspace, each handed to every run of it. /api/keys holds the instance’s keys alone, with no notion of a workspace |
workspace on PUT /api/projects/{folder} |
Workspace on a project’s settings page: moves the project’s checkout, row, the users’ folder grants and the pins to another workspace. No route changed a folder’s ID. left lists the schedules, secrets, env files and workflow steps that still name the old one, pins counts the pins that follow |
workspace:<name> and all scopes on /api/secrets, is_secret, rekeyed on POST /api/workspaces/{folder}/keys/generate and /upload |
Variables per workspace: a workspace’s own, encrypted with the instance’s key and kept from every other workspace, and all for every workspace. is_secret false makes an env entry, whose value the API returns; a secret’s never. A rotation of the instance’s key writes every variable again |
recursive on POST /api/projects and PUT /api/projects/{folder}, shallow on a project row |
Recursive on the clone and configure forms: off lists the project and its top-level folders alone, where discovery otherwise lists every folder of a checkout as deep as they go |
recursive on POST /api/workspaces and PUT /api/workspaces/{folder}, shallow on a workspace row |
Recursive on the workspace forms, the toggle projects have: off lists the workspace and its top-level folders alone; the export carries it |
POST /api/workspaces/{folder}/export, POST /api/workspaces/import |
Export on a workspace’s settings page and Import on the Workspaces page, each a dialog: a workspace moves between instances with its SSH keys, its grants and its secrets, which only the bundle’s passphrase protects inside it and no other route hands out. A POST keeps the passphrase out of the URL; an export’s passphrase takes at least 12 characters |
GET /api/files/{folder}/{path} |
The preview window on a folder page’s Files: a file of the checkout, rendered as /api/artifacts/files/{path} renders an artifact. /api/sources serves only the files a pipeline reads, and no route served the rest |
./<path> as job on POST /api/runs |
Run beside a file that runs on a folder page’s Files and in its window: the file as a job of its folder, under the folder’s allow rules and hours, with the env, keys and pull of any job. A file outside the scripts folder had no job ID. run_job, schedules and workflow steps resolve a job the same way and take it too |
POST /api/keys/encrypt/rotate, encrypt on GET /api/keys |
The instance row of Settings, Keys: the instance’s encryption key, which /api/keys/generate names no rotation for, gets new material, the old kept to decrypt. A key either encrypts or authenticates: an upload of key material the key folder holds, under any name or purpose, answers 409 |
dir and args on POST /api/runs, dir and args on run rows, takes_args and exec on jobs |
A directory inside a folder, such as malta/dockerfiles in the checkout of malta: that directory’s own targets, run there under the folder’s rules, which no folder or job ID named, and arguments for an atkins job that declares args: true, which reach it after -- as argv; exec is the command they follow, the argv of the job’s exec: step, for the field that takes them to show what runs. Run again repeats both. Schema run_dir_args adds the two columns |
GET /api/folders/{id}/actions?dir=, @<skill>:<job> as job, dir on GET /api/runs |
The git line of a folder page and of a directory inside a folder, and the rows of the atkins skills that apply in that directory. /api/folders lists the catalogued jobs only, and no route read git status or asked atkins for skills; a skill job had no job ID. A skill job runs atkins <skill>:<job> under the folder’s rules; run_job, schedules and workflow steps take it too. dir with job on the runs listing keeps one directory’s job apart from the folder’s job of the same name. Read only, for whoever sees the folder |
@shell as job on POST /api/runs |
The @shell job of a folder: an interactive bash -l in a folder’s directory, or a directory inside it, which no job of a folder runs. For admins alone: its allow list names the admin role, no folder rule widens it, and task-ui checks the shell permission, Enter Shell, besides; anyone else gets 403; no schedule or workflow step takes it |
POST /api/prompts |
The Prompt box of every folder page and every directory inside a folder: Claude Code on a prompt in that directory. /api/workspaces/{folder}/prompt names a workspace and no directory inside it; a prompt holds its directory alone, on either route |
GET /api/prompts, parent on POST /api/prompts and /api/workspaces/{folder}/prompt, the note as their answer, workspace_id and dir on notes |
The prompt threads under the Prompt box: a prompt is a question and Claude’s answer, which a run cannot carry. A prompt is a note of the folder’s workspace and place, and Claude replies to it once the run ends. A reply asks again with the thread. /api/notes and list_notes list the board’s notes alone, none with a workspace. Schema note_workspace adds the two columns |
PUT /api/notes/{id}, edited_at on notes |
Click to edit a note, a reply or a prompt: editing a note and running its answer again, which POST and comments cannot express. The note keeps its ID, and the agent’s answer to it takes the new answer in place, from a run under the same run ID. Schema note_edited adds the column |
summarize on POST /api/runs |
** |
queued run state, message on runs |
Host commands wait their turn: without a queue, a command for a host that runs one has nowhere to go, and a schedule, a workflow step or a second Run loses its command. A run of @host:<host> on a busy host is a run row in the state queued, listed with ?state=queued and in the list_runs state filter over MCP, which starts once the runs before it ended; POST /api/runs/{id}/stop or kill takes it out of the queue. More than ten waiting for a host answer 409. message says why a run ended when its state needs saying: a queued run the instance restarted before it ran ends failed with “the instance restarted before it ran”. Schema run_target_queued moves the run table to one whose state check takes queued, with the column message |
GET /inventory/hosts/{name}, a page |
The expanded row of a host on the Inventory page: the host’s containers, durable filesystems and month of charts, which /api/inventory, the systems list, does not carry, read on demand from Beszel’s containers, container_stats and system_stats and over the host’s ssh login. A page the Inventory page fetches, not a route of /api: 403 without hosts, as the facts come over the login |
summarize on POST /api/inventory/run |
Run and summarize on the Inventory page: Claude summarizes the batch of host runs in a prompt thread about its first run once every run ended |
errors on GET /api/folders and in list_jobs, errors on a folder’s sources |
The alert of a broken pipeline on the folder, workspace and dashboard pages: atkins lists no jobs of a pipeline its linter refuses, and without the findings a broken pipeline reads as a folder with no jobs, the same as an empty one. Each finding names its path relative to the folder, its job, issue and detail |
@host:<name> and @host:* as job on POST /api/runs, schedules and workflow steps, batch and runs from POST /api/inventory/run, target and batch_id on runs, batch on GET /api/runs, job=@host:* on run listings, args on schedules and workflow steps |
Host commands as runs: history, recording, recents, schedules. A command on hosts had no run: no record kept who ran what where, its output went with the page, and no schedule or workflow ran it. Each host’s command is a run of the root’s @host:<host>, its target the host and its args the command, under a batch for the hosts started together; POST /api/inventory/run keeps its request. The job ID names the host, as @<skill>:<job> names a skill’s job. A host needs no field of its own, and the colon keeps it apart from an @name mention; @host:* is every host with an ssh login, a batch. args on a schedule or a step go to any job that takes args, which the Scheduled form and the step editor fill with one field; a host job needs them, its command. Every route takes the hosts permission for @host:; the bare @host answers 400 naming the form. Schema run_target, schedule_target and workflow_step_target add the columns, and run_target_host_job, schedule_target_host_job and workflow_step_target_host_job set the job of the rows of the bare @host to that of their target |
GET /api/artifacts/tree/{path}, PUT /api/artifacts/files/{path}, ?raw on GET /api/artifacts/files/{path}, user_name on artifacts, list_artifacts, read_artifact, write_artifact |
Agents outside task-ui read and write the artifact tree, which only runs wrote. The tree route lists a directory as the Artifacts page does, which no route listed, ?raw is the file itself for a client that reads it, and PUT writes a file as the user with no run, the version it replaces kept in the history. Schema artifact_user adds the column, and permission_policy_runner_artifact grants the runner role artifact/edit |
PUT /api/retention, @cleanup as job, 410 on /api/runs/{id}/cast and /summary |
The Data retention settings page: how long the instance keeps run recordings, earlier artifact versions and the secret read log, and how much of each, which no setting held, and @cleanup of the root, task-ui cleanup, which applies the rules daily. A run whose recording expired keeps its row; its cast and summary answer 410 saying so, its frame shows it. Schema setting adds the table |
GET /api/permissions, GET PUT /api/permissions/roles/{role}, GET PUT /api/permissions/users/{name} |
Permissions for roles and users: the rules of config/permissions.yml, a role’s or a user’s policy and what it allows, which no route held. /api/users changes the folders a user sees; these routes change what the user may do |
run on POST /api/prompts and GET /api/prompts, about_run_id on notes |
The Prompt box of a run’s page: a prompt about a run’s output, which a folder’s prompt cannot carry, the run being the context. The prompt runs in the run’s folder and directory with the run’s details and the end of its output, and its thread lists on the run’s page, the run’s history of questions and fixes, apart from the directory’s prompts. Schema note_about_run adds the column |
POST DELETE /api/users/{id}/mcp, mcp on users |
MCP access on a user’s settings page: a token made from the browser, shown once, with an expiry; tokens had no writer but the CLI. One per user, named mcp, lasting 30 days; a token past its expiry answers 401 “the token has expired; rotate it on your profile”. Tokens of task-ui token keep no expiry. Schema user_auth_token_expiry adds the column |
@claude-prompt as job on run rows |
A prompt’s run belongs to the place it ran in, as @shell does: folder, dir and job @claude-prompt, in the recents, the runs list and the folder’s run state, where internal/claude-prompt hid it in a folder only admins see. Runs of internal/claude-prompt stay as they are. POST /api/runs with it answers 400: a prompt starts from /api/prompts, with its thread. Another user’s prompt run lists in /api/runs, get_run and the runs topic with prompt/view; the asker’s own always |
@claude as job on POST /api/runs |
The @claude job of a folder: Claude Code in a terminal in a folder’s directory, or a directory inside it, which no job of a folder runs. A shell as @shell is: for admins alone, 403 for anyone else, in every folder; no schedule or workflow step takes it |
@claude-note as job on run rows |
The Notes agent as a built-in job of the root, internal, where it works, beside the other Claude jobs of every folder, and no folder lists for it. The pages and the MCP text name a run of the root by its job alone, its row keeping the folder internal. POST /api/runs with it answers 400: the board starts it on a note. Its runs list in /api/runs, get_run and the runs topic with note/view; the agent’s user’s own always, which it ends with note/edit. Runs of internal/* stay as they are, for admins and their starter |
DELETE /api/users/{id} |
Delete on the Users page and a user’s page: the password file had no remover but editing it on the server. It removes a user who is no admin and not the caller from the file and storage at once, with their sessions, tokens and policy; 403 for an admin or yourself. It answered 405 before, and still does without a password file to write |
post_note, ask_prompt |
An agent posting and asking as its own user, by its permissions: note_comment answers a note and list_notes reads them, and no tool left a note or put a prompt to Claude in a folder |
prompt, prompt_folder on workflows and /api/workflows, enabled on a step of /api/workflows and disabled on workflow steps, input on POST /api/workflows/{id}/runs and run_workflow, workflow_run on GET /api/prompts, about_workflow_run_id on notes |
A workflow with a prompt: Claude gets the steps’ output once they end, which no route asked. A workflow summarises itself or acts on what the steps printed, or is a prompt alone. A step that stays in the chain and a run skips had no field, and a prompt takes text at run time, which the start had no body for. The prompt’s thread is a note about the workflow run, listed by it and on its page, apart from its place’s prompts. A workflow with no enabled step and no prompt answers 400; a prompt takes prompt/edit to save and to run. Schema workflow_prompt, workflow_step_enabled and note_about_workflow_run add the columns |
prompt and dir on a step of /api/workflows and on workflow steps, prompt and answer on a workflow run’s steps |
Add prompt step in the workflow editor: a step that asks Claude with the steps before it: a workflow fetches with one prompt and reasons over that reply with the next, which a job step cannot carry; a step named a job or a workflow alone. The step’s thread is a note about the workflow run, as the final prompt’s is, and the run’s step keeps Claude’s answer, the step’s output for the prompts after it. A prompt step takes prompt/edit to save and to run. Schema workflow_step_prompt adds the columns |
input and answer on workflow runs |
The Workflow context of a workflow’s prompts: each prompt reads what the workflow’s last three finished runs concluded, to stay consistent, and the run’s input, which no row kept. A run’s answer is Claude’s reply to its final prompt, or to its last prompt step without one. Schema workflow_run_answer adds the columns |
get_workflow, create_workflow, update_workflow, the run’s answer in run_workflow, a run token for every @claude-prompt and @claude-note run |
An agent builds, tests and runs workflows: the tools read, make and change a workflow through /api/workflows, by its routes and permissions, workflow/edit and prompt/edit for a prompt, and run_workflow ends with Claude’s reply to read. A prompt’s run reached no task-ui tools of its own. Each Claude run task-ui starts gets a token of its user, minted at its start and revoked at its end, in an MCP configuration file of the run’s own: the user’s permissions, no MCP access of the user’s needed, shown nowhere. No delete tool |
area on POST /api/prompts, on GET /api/prompts and on notes, area on ask_prompt |
The Prompt box of the Workflows page: Claude builds or changes a workflow with the task tools, tests it and fixes it, an instruction no folder’s prompt carries, and its threads belong to the page. area workflows is the one area; any other answers 400. An agent posts the same prompt over MCP. Schema note_area adds the column |
runs, prompts, workflow_runs and tooling on PUT /api/retention, the ages 90d and 365d |
The Data retention table covers every area the instance keeps: the rows of runs with their summaries and threads, prompt threads and workflow runs grew with no rule to age them out, expired tokens and sessions stayed until their user minted again, and task-ui measured the tool history atkins keeps in each workspace’s home and the instance’s own copy without a rule to age it. Every rule defaults to 31 days; the two longer ages are for run history. The expired sign-ins go at every cleanup with no setting; the cleanup runs atkins --prune on each tool history database, under the age the rule holds |
Removed: GET /api/keys/{name}/private, get_ssh_key, reads and last_read on GET /api/keys, keys on GET /api/me, key_reads on PUT /api/retention, the rule key/read |
Only the ssh agent of a run reads a private key: no API, MCP tool or page returns a private key, and no log records a read of one. The key_read table stays in the schema, unused; no check names a saved policy entry for key/read, and one has no effect. A workspace export still carries the workspace’s own SSH keys inside its passphrase-protected bundle |
Changed: user on POST /api/prompts and POST /api/workspaces/{folder}/prompt |
Runs as in every prompt box and the header’s prompt, for an admin: a prompt runs as the user named, as a workflow’s prompt step runs as the workflow’s user, where every prompt ran as the caller and no field could say otherwise. The thread, its run, the Claude token, the keys and the secrets are that user’s. An admin names anyone, anyone else only themself (403), a name nobody holds 404; the user named holds prompt/edit and sees the place, 403 naming them otherwise; empty, the caller. A reply keeps the thread’s user: user on one answers 400, an admin’s reply runs as the thread’s user, and anyone else but that user answers 403 |
runs_over on POST and PUT /api/workflows and on workflows, @script as job on a workflow step |
Runs over in the workflow editor: a run repeats the steps for each project of a workspace, each step in a project of it moved to each project in turn, for maintenance over many checkouts, which a workflow of fixed folders could not say. Script in the editor: a step runs a shell command the user writes, {"kind": "job", "folder": "malta/apps", "job": "@script", "args": ["go get -u ./..."]}, with shell, 403 without it; @script exists only as a workflow step, as no folder lists it. Schema workflow_runs_over adds the column |
Changed: user on POST and PUT /api/workflows, run_as and run_as_user_id on workflows; POST /api/workflows/{id}/runs and a workflow’s schedule start the run as that user when the workflow has one; user on the schedule of such a workflow answers 400 |
Runs as in the workflow editor: a workflow’s runs can belong to one user, whose permissions, Claude token and workspace keys and secrets every step gets. A daily workflow runs as the user whose checkout it writes to, whoever starts it. An admin names anyone, anyone else only themself (403), a name nobody holds 404; empty, whoever starts it, as every workflow ran before; left out, unchanged. A start takes the caller’s right to run every step, as before, and the workflow’s user’s, 403 naming the step and the user; a user since deleted 409. A step that runs a workflow with a user of its own runs it as that user. The run row’s user_name is the user the run went as. The schedule of a workflow with a user takes no user (400, pointing to the workflow) and fires as that user; one of a workflow without runs as the schedule’s user, picked as on a job’s schedule. Schema workflow_run_as adds the column |
Changed: sidebar_limit on PUT /api/workspaces/{folder} and on workspace rows |
Sidebar in a workspace’s settings: a workspace of many checkouts lists the ones in use, the N projects with the newest commit, which no setting could say; the sidebar is where the cut applies, the Projects page, the workspace’s page and pins keep every project. 0, the default, lists every project by name; below 0 answers 400; left out it stays; a create takes none. Schema workspace_sidebar_limit adds the column |
Changed: POST /api/workspaces/{folder}/export and POST /api/workspaces/import take an admin, where they took workspace/edit |
The bundle carries the workspace’s private keys and secrets, which workspace/edit reaches nowhere else, and the import writes private keys into the key folder: an admin’s call, as the Export and Import buttons show only to admins |
triage on POST and PUT /api/schedules and on schedule rows |
Triage failures with Claude on the schedule forms: a run of the schedule that fails gets a prompt thread on its page, where Claude finds the cause and fixes it in the folder when it can, and a note on the board with the result. An option of the schedule, which the run request POST /api/runs cannot carry and no field held. Schema schedule_triage adds the column |
Changed: workflow on POST /api/schedules, workflow_id on schedule rows |
Schedule at the end of a workflow’s edit page: a schedule starts a workflow, the other thing the scheduler starts, named by its ID with no folder or job, the same resource with the same expression, user, switch and times. The Scheduled page lists both and one menu enables, disables and deletes either. workflow is the workflow’s ID; with it folder, job, args and triage answer 400, a workflow the caller cannot see 404, a step the caller may not run 403, as POST /api/workflows/{id}/runs does; user as for a job, 400 for one who may not run a step. The row carries workflow_id with folder and job empty; PUT switches it and picks its user, and refuses args and triage. Each run starts the workflow as the schedule’s user with no input, skipped while it runs. Schema schedule_workflow adds the column |
user on POST and PUT /api/schedules |
Run as on the schedule forms: a schedule runs as a user an admin picks, by name or ID, where it ran as whoever made it. Anyone but an admin names only themselves (403); a user nobody holds, or one who may not run the job, answers 400. The scheduler checks again when it fires and skips a job its user may not run at that time |
reads and last_read on GET /api/secrets, GET /api/secrets/{id}/reads |
Secret reads: which run got which secret, which nothing recorded. Each run logs one read per secret it gets, its run, folder, dir, job and user, the last 1000 of each secret kept. The list counts them and names the last; the log pages them, newest first. Names only, never a value. Schema secret_read adds the table |
GET /api/keys/{name}/private?recipient=, get_ssh_key, reads and last_read on GET /api/keys, keys on GET /api/me, key_reads on PUT /api/retention |
An agent elsewhere uses an instance-held SSH key for the length of a task without the key crossing in clear: no route handed out a private key. It returns the key encrypted to the age X25519 recipient the agent made for the request, for an MCP agent user with key/read, a rule of the keys section that Allow All leaves out, and a key linked to them or their role, any SSH key for an admin; never an encryption key. Each read lands in a log, its count and last read on the listing, kept by the Data retention rules. Schema key_read adds the table |
name, algo, bits, purpose, git on POST /api/keys/generate, /upload and PUT /api/keys/{name}, purpose, scope, roles, users, git on keys |
The key folder: every key in one flat folder under a name of its own that ends with its algorithm and size, an encryption or an SSH key of the instance or a workspace, which the old id_<type>_<label> file names and the per-workspace folders could not say; one SSH key marked for git, as every key in the folder went to git before. Schema key_info, key_role and key_user add the tables; the first start takes the old files in |
admin, role, mcp, agent_reply on PUT /api/users/{id}, admin, mcp_agent and agent_reply on users |
Roles in the database: the role lists of the environment set what a user is for every start and no page changed it. An admin sets the admin flag, the role, MCP agent user and Run instance Claude when mentioned on a user’s profile; the MCP endpoint takes the tokens of an MCP agent user alone, 403 otherwise, and a note naming a user with agent reply starts the Notes agent as them. mcp names the token on users already, and the flag shows as mcp_agent. Schema user_flags adds the columns |
GET /api/me, whoami, the resource taskui://context |
An agent can’t tell what it may do without trying: whoami and /api/me say who the token’s user is, the role, the permissions the user holds, each area of task-ui with a line on its purpose, from config/context.md, and the folders the user sees. The resource is context.md whole, as the MCP instructions carry it. A 403 names the permission it needs, and over MCP points to whoami |
POST /api/notes/{id}/comments and note_comment by a prompt’s run token, TASK_UI_PROMPT_SOURCE |
A prompt’s answer as a reply Claude posts over MCP in the thread it answers, which the run’s injected context names: a builder replies once its workflow run ended, where the run’s final message came too early. The token comments on its own thread alone, 403 on any other, with the asker’s prompt/edit; the reply is Claude’s, a state changes nothing, and the final message is the reply only when the run posted none |
take and release in, input out, on WS /api/runs/{id}/attach, can_input and input_by on runs |
Take input in the run window: answering a prompt a job that takes no input waits on, such as the ssh prompt to trust a host, where the socket dropped every keystroke to such a job. A window sends take with the input permission, Allow interactive mode, and its keystrokes reach the process until it sends release; the server replies `{“type”:“input”,“state”:“on” |
title on run rows, {"type":"title"} on the attach socket |
A job’s terminal names its current step: a run that sets its title with OSC 0 or 2, printf '\033]0;building images\007', shows it beside its job in the run window, the browser tab, the dashboard and the runs list, live as it changes. No field carried what the terminal says of itself. The runs topic hears each change, at most four a second; the socket sends the title on attach and on every change; get_run and list_runs print a title: line. Schema run_title adds the column, the last title a run set |
chain on GET /api/runs/{id}, the chain message of the attach socket |
The run window follows a chain of runs, a Run and summarize, a workflow run or a batch of host runs, to the next run without a click, with ‹ Previous, Run 2/N and Next › in its foot: no route said which run comes after a run. chain is {id, kind, position, total, prev, next, last, complete}, last the newest run that started when it isn’t this one, where a window opened with View goes, kind one of summary, workflow and batch, total 0 while not known; the socket sends it on attach and, after the status of the run’s end, once the next run started or the chain is complete, and stays open for it while the chain waits |
report on GET /api/runs/{id}, GET /runs/{id}/report, a page |
Report in the run window’s foot and on a run’s page: Claude’s answer of a finished prompt’s run, or the summary of a run, as a document without the terminal. The run view had no word of whether a run has one, and the threads under /api/prompts need the run’s thread to find it. A server-rendered page outside /api, for a user with prompt/view who sees the run |
POST /api/webhooks/forgejo, @forgejo-sync as job |
The Forgejo integration: @forgejo-sync of the root, task-ui forgejo-sync [--dry-run], makes a workspace of every organisation of the token’s user and of every user whose repositories the token reads, the repository <owner>/<owner> its home, every other repository a project, and installs a push hook on each the token administers, for admins alone, as @cleanup is; a user without a readable repository gets no workspace; Sync now and Dry run on the Integrations page start it. The webhook is what the hook posts to: no session, as Forgejo calls it, signed instead with the HMAC-SHA256 of the body under the integration’s webhook secret, which it checks before it reads anything. It mounts beside /api/run/secrets, outside the session and same-origin checks; it only ever pulls the checkouts whose clone URL names the repository pushed, under the lock a run pulls with. 202 {pulled, output}, 200 for a ping or a repository with no checkout, 401 for a bad signature, 404 with the integration off |
GET PUT /api/instance, 429 on starts |
The Runs section of the Instance page: the runs that go at once, the ones that wait, and the backlog, backlog_length_auto, 20 per CPU, backlog_length, the length an admin sets, empty or 0 for the automatic one, and backlog_length_effective. No route read the slots or held a cap on the line. A start that would wait behind a full backlog answers 429, and run_job and run_workflow the same error. PUT is for admins; the value lives in the setting table as runs.backlog_length |
parent_env on GET PUT /api/instance |
Parent environment on the Instance page: how much of the server’s environment a run starts with, none, auto or all. No field held it; a run took the whole environment, credentials included. PUT is for admins; the value lives in the setting table as runs.parent_env, and schema setting_parent_env sets all on an instance that had runs before it |
GET /api/run/secrets/{name} |
task-ui get-secret <name> in a run: a secret that reaches the run’s folder, for a script that reads it at the point it needs it. No route took a run’s own token: it signs in this route alone, lives as long as its run, and the read lands in the secret’s read log as the run’s environment’s reads do. 401 for a token of no running run, 404 for a name the folder’s secrets lack |
POST /api/keys/{name}/rotate, DELETE /api/keys/{name}/backup, backup, backup_fingerprint and rotated_at on keys |
Rotate key and Delete backup in a key’s menu on the Instance page: an SSH key, the git key above all, had no rotation, and /api/keys/encrypt/rotate names the encryption key alone. A rotation keeps the previous material as the key’s one backup, backup/<name>, which serves beside the new key until an admin deletes it; a key with a backup answers 409 “delete the backup first”. The encryption key re-encrypts the instance’s secrets as /api/keys/encrypt/rotate does, rekeyed the count. Schema key_info_backup adds the columns |
Removed: encrypt on GET /api/workspaces/{folder}/keys, "encrypt": true on its /keys/generate and /keys/upload (400), rekeyed there, the 409 of POST /api/secrets for a workspace without a key |
One encryption key per instance: the instance’s encrypts every secret, the workspaces’ and the integrations’. A workspace has no encryption key to list, make, rotate or lack. A start re-encrypts the secrets of the workspaces’ keys onto the instance’s and removes those keys. An export carries a workspace’s secrets inside its passphrase, and its SSH keys alone |
kind on a step of /api/workflows, on workflow steps and on a workflow run’s steps, the summary kind, description on workflows; prompt and prompt_folder on a workflow read empty, and on a request they become its last prompt step |
The workflow editor’s step list: a workflow is a chain of steps, a command, a prompt or a summary, in any number and order, from a blank workflow. A final prompt beside the steps had a place of its own that no step could move around or repeat, and a summary of the steps had no step that runs after a failure to say why. kind is job, workflow, prompt or summary; left out, the fields say which, as before. A summary step takes no prompt: Claude gets task-ui’s summary instruction, the one of Run and summarize, with the steps before it. The last answer of a prompt or a summary step is the run’s answer. A request’s prompt stays for a client that sends one and becomes the last prompt step, and no client breaks; the columns stay, empty. Schema workflow_step_with_kind adds kind and turns each workflow’s prompt into its last prompt step, workflow_description adds description |
Removed: /api/env/{folder} and /api/env/{folder}/{path}, env_files on the jobs of GET /api/folders and GET /api/folders/{id}/actions |
/settings/secrets alone manages variables: GET POST DELETE /api/secrets, with is_secret for an env entry and value in its answer. task-ui writes no dotenv file into a folder before a run. Existing env file rows stay for a workspace export and import |
Content-Disposition: inline on GET /api/runs/{id}/cast |
A recording is for replay on the run’s page: the route serves the .cast inline to the page’s ** |
home_project on PUT /api/workspaces/{folder}, url, project and branch optional on POST /api/workspaces, home on POST /api/projects, home_project_id on a workspace row, source_url and branch off it, home_project in the export’s manifest |
Home project on a workspace’s settings page, Change, Add or Replace, and Remove: a workspace is a plain directory of sibling checkouts, and its runs get the home project’s directory as HOME, where atkins finds ~/.atkins/skills. No field named a project as the workspace’s, and the workspace’s own repository leaves its row for the home project’s. Deleting the home project unsets it. Schema workspace_home_project adds the column and drops the two; the bundle format is 3 |
PUT and DELETE /api/workspaces/{folder}/home {url, branch}, home_url and home_branch on a workspace row, home in the export’s manifest and in the import’s answer |
Home repository on a workspace’s settings page, Replace or Clone, and Remove: a workspace is home/, the HOME of every run in it, beside src/, its projects, and the home is a folder of its own: no project route names it. PUT replaces the home with a clone, DELETE empties it, both 409 while a run of the workspace is active. url and branch on POST /api/workspaces clone the home. Schema workspace_home_url adds the columns, fills them from the home project’s row and deletes that row; the bundle format is 4 |
Removed: home_project on PUT /api/workspaces/{folder}, project on POST /api/workspaces and project in its answer, home on POST /api/projects, home_project in the export’s manifest; home_project_id stays on the row, empty |
The home is no project: a workspace’s runs get home/ as HOME with a checkout in it or without, and a project never becomes the home. The bundle format is 4; an older bundle answers 400 |
Removed: hours and job_hours on PUT /api/projects/{folder} and on project rows; added hours on POST /api/workspaces and PUT /api/workspaces/{folder} and on workspace rows |
Hours on a workspace’s settings page: one rule per workspace gates every run in its folders, the built-ins too, where each project and each job carried hours of its own. A project request that still sends hours or job_hours has them ignored; hours the hours package does not read answer 400 on the workspace routes. The export carries a workspace’s hours with its row |
Changed: folder at the top level of the body POST /api/workspaces returns |
Add workspace lands on the new workspace’s page: the form’s data-next fills {folder} from that body, and workspace is an object a data-next cannot read a field of |
Removed: PUT /api/workspaces/{folder}/keys/{name}, policies and users on its /keys/generate and /keys/upload, the page /settings/workspaces/<folder>/keys/<name> |
A key of a workspace goes to every run of that workspace. It was added to one workspace, and asking a second time who inside the workspace may have it was a question with no answer anyone wanted to give: the scope is the grant. An instance key still links, because it reaches every workspace and the links are the only thing that narrow it. The links a bundle an older instance wrote carries are read, kept and no longer handed out |
| Method | Path | Does |
|---|---|---|
GET |
/api/folders?folder= |
the folders you can see and their jobs, with can_run and last_run |
GET |
/api/folders/{id}/actions?dir= |
the git line and the skills of a folder, or of dir inside it: git (checkout, branch or detached, upstream, ahead, behind, dirty, and changes, the changed and untracked files with index, worktree, path, and from for a rename or copy or conflict for an unmerged file), skills with their dir, when and jobs, and the place’s own jobs, each job with can_run and last_run; 404 for a directory the folder’s Files would not open |
GET |
/api/runs?folder=&job=&dir=&state=&batch=&limit= |
the runs, newest first, with live, attached, can_run, and while one runs can_input and input_by; with job, dir names the directory of the job, empty for the folder’s own; job=@host:* keeps the runs of every host job; batch keeps the runs of one batch |
POST |
/api/runs {folder, dir, job, args, force, reason, cols, rows, summarize} |
starts a run; job is a job’s ID, ./<path> for a file of the folder that runs, executable or with a shebang, and not a dot file, or @<skill>:<job> for a job of an atkins skill that applies in dir, which runs where the skill matched, or @shell, an interactive bash -l in the folder or dir, or @claude, Claude Code in one, for admins alone, 403 for anyone else, or @host:<name> of the folder internal, the command in args on a host of the inventory, or @host:* for a batch on every host with an ssh login, 403 without the hosts permission, 400 for the bare @host; @claude-prompt and @claude-note answer 400; dir, a slash path inside the folder, makes job one of that directory’s own targets, run there, 404 for a directory the folder’s Files would not open; args, a line split as a shell splits words with nothing expanded, or a list, at most 32 of at most 512 bytes, follow the job’s argv after --, 400 for a job without takes_args; 409 with the run in the way unless forced; 403 out of hours unless forced with a reason; summarize asks Claude, when the run ends, for a thread about it on its page, as you, the question “Summarize this run.” (see GET /api/prompts?run=), 403 without prompt/edit, 400 for @shell, @claude or an interactive job, and nothing when the server restarts before the run ends |
GET |
/api/runs/{id} |
one run, of a folder you see: a prompt’s run, @claude-prompt, with prompt/view, the Notes agent’s, @claude-note, with note/view, and such runs you started always |
POST |
/api/runs/{id}/stop, /kill |
ends a run |
GET |
/api/runs/{id}/frame.svg, /frame.txt, /cast |
the last frame; the recording, inline, for the replay and the output on the run’s page |
WS |
/api/runs/{id}/attach |
binary frames are output; text frames are `{“type”:“input” |
GET DELETE |
/api/artifacts, /api/artifacts/{id} |
list, download a version, delete a record; DELETE /api/artifacts empties the tree, its history and the records (artifact/delete) |
GET POST |
/api/runs/{id}/summary |
a finished run’s summary: GET the kept one, 404 without; POST returns the kept one or has Claude write it from the end of the output and keeps it; summary as markdown, html, kept (Claude Code integration) |
GET POST |
/api/workflow-runs/{id}/summary |
a finished workflow run’s summary, as for a run: Claude reads each step’s job, state and the end of its output; 404 for a run with a step you may not see |
GET DELETE |
/api/artifacts/files/{path}?version= |
a file of the tree: kind (image, markdown, text or file), markdown as html, text as text, and its versions, each with its run, or user_name for one a user wrote; ?raw answers the file itself, its version in X-Artifact-Version; DELETE takes a file or a directory out of the tree, the history keeps it (artifact/delete for a record, artifact/edit for a file of the tree) |
PUT |
/api/artifacts/files/{path} |
writes the raw body, at most 16 MiB, into the tree as the user, with no run, replacing the version there, which the history keeps: 201 and the record, user_name set; 400 for a path with .., an empty element, a leading / or an element starting with a dot, 409 where the tree holds a directory at the path, or a file where the path needs a directory (artifact/edit) |
GET PUT |
/api/instance |
the settings the Instance page saves: parallel, the runs that go at once, running, queued, backlog_length_auto, 20 per CPU, backlog_length, the one an admin set, 0 for none, backlog_length_effective, and parent_env, none, auto or all; PUT {backlog_length, parent_env}, each optional, backlog_length a whole number from 1 to 100000 or empty for the automatic one, 400 otherwise, answers the settings as they stand (admins) |
PUT |
/api/retention |
the Data retention rules, {recordings, runs, prompts, workflow_runs, artifacts, secret_reads, tooling}, each {age, limit}: age 24h, 7d, 31d, 90d, 365d or never, limit a size such as 500MB or the bytes, empty or 0 for none, read for recordings, runs and artifacts alone; an area left out keeps its rule; answers the rules as they stand, limit in bytes; 400 for an age or a size it can’t read (admins) |
GET PUT |
/api/telemetry |
what the instance records of its own work, the yaml names of oida.Options: {enabled, service_name, sample_rate, ring_buffer_size, top_requests, max_spans_per_trace, refresh_interval, live_stream, capture_logs, track_memory_use, trust_request_id, path, ignore_paths, allowed_networks}; a setting left out keeps its value, the two lists are one entry a line and a blank line is no entry; enabled alone keeps the traces recorded and anything else empties the ring buffer; path takes a restart; answers the settings as they stand, 400 for a sample rate outside 0 to 100 or a path that is not absolute (admins; anyone who may see a step of the settings reads) |
GET PUT |
/api/sidebar |
the sidebar order, {items: [href...], shown: {href: bool}}: the order is the array order, a link the table does not hold is left alone, and a link shown leaves out keeps the flag it had; answers the rows as they stand (admins; anyone who may see a step of the settings reads) |
GET |
/api/modules |
the plugins written in PHP, by name, each {name, vendor, plugin, title, description, icon, version, href} (module) |
POST |
/api/modules/reload |
reads every module root again and runs each modinfo.php: a module added to a checkout appears and one whose directory is gone loses its rows; answers what registered (admins) |
GET |
/api/artifacts/tree, /api/artifacts/tree/{path} |
a directory of the tree, the latest versions: {path, entries}, each name, path, dir, byte_size, modified, and for a file the run_id, folder and job that wrote it, or user_name; the files of folders the user sees, and the ones users wrote (artifact/view) |
GET POST PUT DELETE |
/api/schedules, /api/schedules/{id} |
{folder, job, expression, enabled, user, args, triage}, or {workflow, expression, enabled, user} for a schedule of a workflow, the workflow’s ID, whose row has workflow_id and no folder or job; args for a job that takes args, the command of internal’s @host:<name> or @host:*, which needs it (hosts) |
GET POST PUT DELETE |
/api/workflows, /api/workflows/{id} |
{name, description, steps: [{kind, folder, job, workflow, enabled, args, prompt, dir}], sidebar}, the steps in the order they run; kind job takes folder and job, and args for a job that takes args, as on schedules; workflow takes workflow, its ID; prompt takes prompt and asks Claude in folder, internal or empty for the root, and dir inside it, with the output of the steps before it; summary asks there with no prompt, task-ui’s summary of the steps before it, and runs after a failed step too; a prompt or summary row’s job is @claude-prompt, 403 without prompt/edit, and a run’s step keeps its kind, prompt and Claude’s answer; kind left out is prompt with a prompt, workflow with a workflow, else job; a step with enabled false stays and a run skips it, its row disabled; prompt and prompt_folder on a request add a last prompt step there; 400 naming the step, step 2: ..., for a step its kind refuses, and with no enabled step; user names who its runs run as, an admin anyone, anyone else themself (403), unknown 404, empty whoever starts it, left out unchanged, read back as run_as and run_as_user_id; runs_over names a workspace whose projects each run repeats the steps for, 400 for one you don’t see and with a workflow step; a job step of @script runs its one arg with bash -l -c in folder, with shell; owner or admin changes |
GET POST |
/api/workflows/{id}/runs |
runs, newest first, with their steps; POST {input}, the body optional, starts one as the workflow’s run_as user, input going to its prompt and summary steps, a prompt’s under Input:; the run’s answer is the last answer of a prompt or summary step; 403 for a step you may not run, or a workflow with a prompt or summary step without prompt/edit, and 403 naming the step and the user when the run-as user may not run it, 409 for a run-as user since deleted and while one runs |
GET |
/api/workflow-runs?limit= |
the runs of every workflow, newest first, 10 unless limit, those whose steps all ran in folders you see, each with its input and answer (workflow/view) |
POST |
/api/workflow-runs/{id}/stop |
stops the step running, skips the rest |
GET POST |
/api/notes?mention=&state=&limit= |
the threads, newest first, with their replies, * or empty for any user or state; POST {parent_id, body} leaves a note or a reply, and one naming the agent starts it |
POST |
/api/notes/{id}/comments |
{body, state}: a reply, and the instruction’s state: working, done or failed |
PUT |
/api/notes/{id} |
{body}: a new body for a note or a reply, its author’s or any for an admin, marked edited_at; a note to the agent, or a prompt, runs again and its answer changes in place under the same run |
DELETE |
/api/notes/{id} |
a note and its replies; its author or an admin |
GET PUT DELETE |
/api/integrations, /api/integrations/{id} |
the integrations, claude, openrouter, forgejo and beszel, each kind with its fields and steps, how to get its key, as the Integrations page lists them: {values} by setting name; a field names the environment variable it sets, env, its default, and source when it takes another field’s value; toggle adds a switch to it, on with default_on, and empty sends the variable with no value; a switch saves as values.<name>.enabled, 1 or 0, and reads back in switches; secrets come back only as set, and an empty one keeps its value, or for forgejo’s secret takes a generated one on the first save; a setting with options takes one of them, the first when empty (integration/view, PUT integration/edit and DELETE integration/delete; for beszel also inventory/edit and inventory/delete) |
POST |
/api/webhooks/forgejo |
what the push hook of the Forgejo integration posts to, no session: X-Forgejo-Signature, the HMAC-SHA256 of the body under the webhook secret, signs it in; X-Forgejo-Event push pulls the checkouts of repository, 202 {pulled, output}, 200 {message} for a ping or a repository with no checkout, 401 for a bad signature, 404 with the integration off, 502 for a pull that fails |
GET |
/api/inventory |
the hosts of the Beszel integration, largest first; 404 without it (inventory/view) |
POST |
/api/inventory/run |
{command, hosts, summarize}: runs the command on the hosts over their ssh logins at once, a run of @host:<host> a host, every host with a login when hosts is empty; 201 {batch, runs}, the batch’s ID and its runs; 400 for no command or a host the inventory doesn’t list (hosts). With summarize, a prompt thread about the batch’s first run, once every run ended (prompt/edit) |
GET POST PUT DELETE |
/api/projects, /api/projects/{folder} |
clone {url, id, branch, workspace, recursive} into <workspace>/src/<id> when workspace names one, into <root>/src/<id> for internal or empty; 409 for a workspace’s name or a directory there, 400 for home, src, internal or internal, and 400 “pick a workspace” for internal or empty on an instance without a default workspace; configure {title, description, allow, sources, commands, job_allow, hours, job_hours, workspace, recursive}; a workspace other than the project’s moves it, answering the row with pins, the pins of every user that follow it, and left, and 409 for a workspace, a missing target, a taken ID or a run going (project/view, POST and PUT project/edit, DELETE project/delete) |
GET POST PUT DELETE |
/api/workspaces, /api/workspaces/{folder} |
make {folder, title, description, recursive, hours} at <root>/<folder> with home/ and src/ in it, its SSH keys in the key folder and no encryption key of its own, and with {url, branch} clone the home repository into home/; 201 {workspace, output}; 400 for home, src or internal, 409 for a folder a workspace, a project or a directory holds, 502 with git’s output for a clone that fails; PUT {title, description, sort, recursive}, the lowest sort first, left out it stays; DELETE removes the projects inside it with their rows, their managed env files and its keys, and the directory with everything in it, 409 while a run of the workspace is active or while it holds a variable, “delete them first”; the default workspace internal, the root, lists first, keeps its sort, and its DELETE removes it like any other, the root’s home/ and src/ with everything in them and its row, the root directory and the named workspaces staying: the instance then has no default workspace, and nothing brings it back. A row carries home_url and home_branch, empty for an empty home (workspace/view, POST and PUT workspace/edit, DELETE workspace/delete) |
PUT DELETE |
/api/workspaces/{folder}/home {url, branch} |
PUT removes the home and clones the repository in its place, 200 {workspace, output}, 502 with git’s output for a clone that fails, the home then empty; DELETE removes the checkout and leaves the home an empty directory, 200 {workspace, output}; both 409 while a run of the workspace is active, 400 for no URL (workspace/edit) |
POST |
/api/workspaces/{folder}/prompt {prompt, parent, user} |
puts a prompt to Claude in the workspace, as /api/prompts does in a folder: @claude-prompt of the workspace as you, or as user names, Claude in its home, TASK_UI_HOME; 201 with the note, whose dir is the home’s folder, malta/home, home for the root; 400 for an empty prompt (prompt/edit) |
POST |
/api/prompts {folder, dir, run, prompt, parent, area, user} |
area workflows asks as the Workflows page’s box, in folder and dir, the root unless given, with the instruction to build, run and fix the workflow the prompt describes, the note’s area; otherwise puts a prompt to Claude in the folder’s directory, or dir inside it, folder internal for the root, and answers 201 with the note: state open, dir the place, such as malta/dockerfiles, workspace_id the folder’s workspace. task-ui runs @claude-prompt there as you, or as the user user names, the thread’s and the run’s user: an admin names anyone, anyone else only themself (403), a name nobody holds 404, one without prompt/edit or sight of the place 403; a run of that folder and dir, with the thread in TASK_UI_PROMPT and the directory in TASK_UI_PROMPT_DIR; the note moves to working with its run_id, then done or failed, and Claude’s answer, in markdown, is a reply to it. One prompt runs per directory and another there waits its turn. run, a run’s ID with no folder or dir, asks about that run in its folder and directory, the note’s about_run_id: Claude also gets the run, the job’s command and the end of its output in the file TASK_UI_PROMPT_RUN names, and 404 answers a run you do not see or one of Claude’s own. parent, a prompt’s ID, replies in its thread and asks again with the whole thread, as the thread’s user, by that user or an admin (403), and takes no user (400); 400 for an empty prompt, 404 for a directory the folder’s Files would not open (prompt/edit) |
GET |
/api/prompts?folder=&dir=&run=&workflow_run=&area=&limit= |
the prompt threads of a folder, or of dir inside it, folder internal for the root, or with area workflows those of the Workflows page in places you see, or with run those about that run, or with workflow_run the thread of that workflow run’s prompt, newest first, each with its replies; a folder’s threads leave out those about its runs and workflow runs; 20 unless limit (prompt/view) |
POST |
/api/workspaces/{folder}/export {passphrase}, /api/workspaces/import (multipart: bundle, passphrase, folder) |
export answers the workspace as <folder>.tui.age, age-encrypted to the passphrase, at least 12 characters: manifest.json with its row, home, projects, env files, grants and secrets in plain text, and .ssh/<name>.key and .pub for its own SSH keys, never an instance key or an encryption key; import makes the directory folder, the bundle’s own when left out and required for internal, with its home and src, adds the rows, encrypts the secrets with the instance’s encryption key, clones the home repository and its projects and answers 201 {workspace, home: {folder, cloned, output}, projects: [{folder, cloned, output}], unknown_users}; 400 for a short or wrong passphrase or a bundle it refuses, 409 for a folder held and for bundled keys held here, at most 64 MiB (admins: the bundle holds private keys and secrets) |
GET POST DELETE |
/api/workspaces/{folder}/keys, /keys/generate, /keys/upload, /keys/{name} |
a workspace’s SSH keys, public halves and fingerprints only: keys; generate {name, algo, bits, comment}, upload {content, name, comment}, named workspace_<folder>_<name>_<algo>, name empty as ssh; every key goes to every run in the workspace and nothing links one, so there is no PUT; DELETE retires a key; encrypt: true answers 400, one encryption key per instance; 409 for key material the key folder holds (workspace/view, the rest workspace/edit) |
GET POST PUT DELETE |
/api/users, /api/users/{id} |
POST {name, password, folders} adds a user to the password file (user/edit; 409 for a name it holds; the user comes in as a viewer); PUT {folders}, or, by an admin, PUT {admin, role, mcp, agent_reply}, each optional: the admin flag, the role without it, runner or viewer, MCP agent user, whose turning off ends the user’s MCP token, and Run instance Claude when mentioned, which needs mcp; 403 for anyone but an admin, 409 “the last admin keeps the admin flag”, 400 for another role or agent reply without MCP. A user shows role, admin, mcp_agent and agent_reply. DELETE takes user/delete and removes the user’s line and the user, with their sessions, tokens and policy, at once, 204; 403 for an admin and for yourself, 404 for a user it does not hold, 405 without a password file to write, where removing a user is removing its line |
POST DELETE |
/api/users/{id}/mcp |
a user’s MCP token: POST makes a new one and retires the one they had, which stops signing in at once, and answers 201 {token, expires_at}, the one answer that holds a token; DELETE removes it, 204. user/edit manages anyone’s; a user rotates or disables their own once on, and enabling your own takes user/edit; 403 otherwise, 409 for a user without MCP agent user, 404 for a user it does not hold. A user as /api/users shows one has mcp: {enabled, created_at, expires_at, used_at}, never the hash |
GET |
/api/permissions |
the rules of config/permissions.yml for the permission matrix: rules, sections and permissions, each {key, title} in file order, paths, every path a policy sets, and roles (user/view) |
GET |
/api/me |
what you may do: {name, role, admin, allowed, summary, sections, permissions, folder_count, folders}, allowed the paths of config/permissions.yml you hold, summary them in a line, each of sections and permissions an area {key, title, about, rules, allowed} with the sentence of config/context.md on it, and the first 100 folders you see; for anyone signed in |
GET |
/api/pins |
your pins, oldest first: {pins: [{id, folder, job, dir, args, created_at}]}; for anyone signed in |
POST |
/api/pins |
pins {folder, job?, dir?, args?} to your sidebar: a folder you see or a directory inside one, or a job of it with its directory and args as a run takes them; 201 with the pin, the one there is for the same target; 404 for what isn’t there or you don’t see |
DELETE |
/api/pins/{id} |
unpins one of your pins; 204, 404 for none of yours |
GET PUT |
/api/permissions/roles/{role}, /api/permissions/users/{name} |
a role’s or a user’s policy: {kind, subject, role, fixed, policy, role_policy, effective}, policy a path to allow or deny, a user’s with its role’s in role_policy, effective what the subject may do by every path a check names. `PUT {path: “allow” |
GET POST DELETE |
/api/secrets, /api/secrets/{id} |
{id, scope, name, content, is_secret}; scope is all, every workspace, workspace:<name>, or a folder of older rows; empty defaults to all; is_secret false makes an env entry, whose value the list and the saved answer carry, a secret’s value never shows; id replaces that row, and its scope and name may change; 400 for an unknown workspace or folder; the instance’s one encryption key encrypts both kinds |
GET |
/api/secrets/{id}/reads?limit=&before=&after= |
the runs that got a secret, newest first, limit (50, at most 200) at a time, before or after a read’s ID to page: run_id, folder, dir, job, user_name, created_at; GET /api/secrets adds reads, their count, and last_read to each secret; 404 for an unknown secret (secret/view) |
POST |
/api/secrets/rekey |
writes every secret again for the instance’s current encryption key |
GET POST PUT DELETE |
/api/keys, /api/keys/generate, /api/keys/upload, /api/keys/{name} |
every key of the key folder, TASK_UI_SSH_DIR, public halves and fingerprints only: {name, purpose, scope, algo, bits, fingerprint, comment, public_key, created_at, roles, users, git}; generate {name, algo, bits, purpose, comment, policies, users}, algo ed25519, ecdsa of 256, 384 or 521 bits, rsa of 2048, 3072 or 4096, purpose ssh or encrypt; upload {content, name, purpose, comment, policies, users}, the algorithm and the size read from the key; the name is <name>_<algo><bits>, unique across the folder, 409 for one taken or key material held; PUT sets {policies, users, git}, the roles and users whose runs get an SSH key and whether git pulls with it; DELETE retires an SSH key, 409 for an encryption key; GET adds encrypt, the instance’s encryption key, and git, the git key’s name (key/view, DELETE key/delete, the rest key/edit) |
POST DELETE |
/api/keys/{name}/rotate, /api/keys/{name}/backup |
POST rotates a key to new material of the same algorithm and size under its name, the previous material kept as its one backup, backup/<name>, which serves beside the new key, backup, backup_fingerprint and rotated_at on the key; the encryption key writes every secret again, rekeyed the count; 409 “delete the backup first” for a key with a backup. DELETE removes the backup’s files and record, 204; 404 for a key without one (key/edit) |
POST |
/api/keys/encrypt/rotate |
rotates the instance’s encryption key to new material, or makes it when there is none, keeps the old as a backup and writes the integrations’ settings again, 201 {key, rekeyed}, rekey_error when that fails and the backup still opens them (key/edit) |
GET |
/api/sources/{folder}?file= |
a file the folder’s sources read, such as atkins.yml, or a file they include: {file, content, html}, coloured, at most 1 MiB, for whoever sees the folder; 404 for any other file |
GET |
/api/sse/{topic} |
server-sent events of a topic, event: the name and data: the JSON: runs, every run as it starts and ends, of the folders you see, a prompt’s with prompt/view; notes, every note and reply as it changes, with note/view, a prompt’s with prompt/view; workflow:{id}, a workflow’s runs, steps and end, for whoever sees every step |
POST |
/api/mcp |
the MCP server, as the token’s user (see MCP) |
GET |
/api/files/{folder}/{path} |
a file of a folder’s checkout, for whoever sees the folder: kind (image, markdown, text or file), markdown as html, text as text and coloured as html, truncated past 1 MiB; 404 for a directory, a path with .., a symlink out of the checkout, .git or a path through it, and any dot file, such as .env or .claude/.secret, or a symlink to one; a dot directory such as .claude/ opens. /files/{folder}/{path} is the file itself, ?download to save it |
GET |
/api/run/secrets/{name} |
a secret that reaches the run’s folder, for task-ui get-secret <name> in a run: the run’s own token, TASK_UI_RUN_TOKEN, signs this route in and no other, and the read lands in the secret’s read log under the run; 401 for a token of no running run, 404 for a name the folder’s secrets lack. Mounted beside the API, as /api/mcp is |
MCP
/api/mcp is an MCP server over the streamable HTTP transport: nineteen tools to say what the token’s user may do, list jobs and workflows, run them, make and change workflows, read the runs, read, leave and answer the notes, ask Claude in a folder, and list, read and write the artifact tree, as the token’s user, and one resource, taskui://context. Its use is in the MCP chapter.
A request your permissions refuse answers 403 with the permission it needs, as {"error": "you need the artifact/view permission"}.