Local workers (CLI)
Overview
A local worker is a function in a file on your PC. The 3Min API CLI (npm package @3minapi/cli) runs on that PC, fetches the tasks your AI sends, runs the function with each task's input and reports the outcome. The AI and your PC never talk directly — 3Min API sits between them.
- No public URL, no open port. The CLI only makes outgoing requests
- What the function reads stays on your PC, except what it returns
- One file = one function = one worker
For a server at a public URL, see Tasks (public workers).
| Term | Meaning |
|---|---|
| Host | A PC running the CLI. Add one per PC on Dashboard → Tasks → Local |
| Host token | tm_host_…. Logs the CLI on that PC in. Shown once |
| Project folder | ~/3minapi by default: 3minapi.json, workers/, data.db, .gitignore. One per host |
data.db |
SQLite file in the project. Every result is saved here; your workers can keep their own data |
| Results reader | A built-in local worker the CLI registers so your AI can read results saved in data.db |
| Kind | kind in worker_list |
How a task reaches it | On Free | API calls |
|---|---|---|---|---|
| Public worker | "public" |
3Min API POSTs it to your URL | 1 | Deliveries count 0 |
| Local worker | "local" |
The CLI fetches it (live signal by default, or polling) | Unlimited | Live: no calls while idle. Poll: every fetch counts 1, even when empty |
Install and update
Commands run in a terminal on your PC — Terminal or PowerShell on Windows (Start menu), Terminal on macOS (Applications → Utilities).
- Node.js 24.12 or newer.
node -vshows the version. If it is missing or older, install the LTS from nodejs.org (orwinget install OpenJS.NodeJS.LTSon Windows), then open a new terminal - The CLI:
npm install -g @3minapi/cli
3minapi --version
- Windows PowerShell may refuse with "running scripts is disabled on this system". Type
3minapi.cmdinstead of3minapi— it works without changing anything. If you would rather allow scripts for your own account,Set-ExecutionPolicy -Scope CurrentUser RemoteSigneddoes that (the setting stays until you change it back) npx @3minapi/cli <command>runs it without installing- Update with the same command:
npm install -g @3minapi/cli 3minapi doctorchecks Node.js, the config, the token, the network and proxy settings, and says what to fix. Behind a proxy, setNODE_USE_ENV_PROXY=1
Quick start
The same order as the setup card on the Local tab.
npm install -g @3minapi/cli
3minapi init
3minapi worker new hello
3minapi worker run hello --input '{"message":"hi"}'
3minapi login
3minapi start
3minapi initcreates the project at~/3minapi(3minapi init <folder>to choose). Commands then work from any folder3minapi worker new hellocreatesworkers/hello.mjsfrom a template. Edit the description first, then the handler3minapi worker run hello --input '…'runs it once on your PC — no task, no API call. Repeat until it does the right thing- Dashboard → Tasks → Local →
Add host, copy the token, then3minapi loginand paste it at the prompt 3minapi startregisters your worker files as local workers and runs their tasks. Your AI now sees them inworker_listwithkind: "local"
start exits right away if there is no worker file.
Commands
All commands work from any folder. --json gives machine-readable output; 3minapi help <command> shows every flag.
Host
| Command | What it does |
|---|---|
3minapi init [folder] |
Creates the host's project. Safe to run again. Without a terminal it needs --yes; --move moves it |
3minapi login |
Saves the host token (masked prompt). --with-token reads it from stdin for scripts |
3minapi logout |
Removes the saved token from this PC only |
3minapi start |
Registers the worker files and runs their tasks until stopped. --mode poll fetches on an interval instead of the default live mode. Ctrl+C stops it |
3minapi start --detach |
The same in the background. It does not start again after a reboot |
3minapi reload |
Makes the running start read the worker files again and register them. Running tasks keep going. 1 call |
3minapi stop |
Stops this PC's start, wherever it was started |
3minapi status |
Whether start is running, local workers, repeating tasks, quota left. 1 call |
3minapi logs [-f] |
The CLI's own log — fetches, stops, errors |
3minapi doctor |
Environment check. No token sent, no quota used |
3minapi paths |
Absolute paths of the project files and the CLI's own folders |
Workers
| Command | What it does |
|---|---|
3minapi worker new <name> [--ts] |
Creates workers/<name>.mjs (.mts with --ts). Registers it at once if start is running (--no-reload skips) |
3minapi worker run <name> --input '<json>' |
Runs it once locally. --input-file <file> avoids shell quoting problems on Windows |
3minapi worker list |
The project's workers, each checked to load. No API call |
3minapi worker logs <name> [-f] |
One worker's log: ctx.log, console output, errors with stack traces |
3minapi worker delete <name> |
Deletes the local worker and its task history from 3Min API, then its file. Cannot be undone |
Results and history
| Command | What it does |
|---|---|
3minapi worker results list |
Saved results, newest first. --worker, --since, -n |
3minapi worker results show <task_id> |
One saved result in full. --run <n> for a run of a repeating task |
3minapi worker results runs <task_id> |
The runs of a repeating task |
3minapi worker results describe <task_id> |
The paths one result has. --worker <name> groups a worker's results by shape |
3minapi worker results query "<SQL>" |
Read-only SQL over saved results. No API call |
3minapi worker results prune --before <date> |
Deletes saved results older than that date from data.db |
3minapi tasks prune --before <date> |
Deletes this host's record of tasks finished before that date. Recent and unfinished ones stay |
3minapi dashboard |
A read-only page in your browser: runs, results, workers, logs. No login, no API call |
dashboard --detachkeeps it running in the background;dashboard urlprints the link again;dashboard stopends itdashboard --lanalso serves other devices on your network over plain HTTP. Use it only on a network you trust- Destructive commands and
--lanask to confirm. Without a terminal (or with--json) they need--yes
Writing a worker
One file in workers/ is one worker, named after the file.
- Name: lowercase letters, digits,
_and-, up to 50 characters, not starting with_, not a Windows device name (con,aux, …). Unique across all your workers — public ones and those on your other PCs — ignoring case .mjs(default) or.mts/.ts— TypeScript runs without a build step (noenumornamespace)
// workers/rename_photos.mjs
export const description =
'Renames the photos in a folder by the date taken. input: { folder: string }';
export const timeoutMs = 10 * 60_000; // optional — default 60 seconds, at most 23 hours
export async function handler(input, ctx) {
await ctx.report('Scanning the folder'); // progress — task_get shows it
if (ctx.signal.aborted) return; // cancelled, or about to time out
// … the work …
return { renamed: 42 }; // → completed; this is the result
// throw new Error('Folder not found') // → failed; the message is the error
}
| Export | Rule |
|---|---|
description |
Required. What the AI sees in worker_list, up to 500 characters. Put the input shape here |
timeoutMs |
Optional. Default 60 seconds, at most 23 hours |
inputSchema |
Optional. A small JSON Schema subset (type, properties, required, enum, items), checked on your PC only |
handler |
Required. async (input, ctx) => result |
ctx |
What it is |
|---|---|
ctx.report(message) |
Progress message, cut at 256 characters. 1 call each |
ctx.log(message) |
Writes to the worker log only |
ctx.env |
System environment + the project's .env (.env wins). Variables starting with THREEMINAPI_ are removed |
ctx.signal |
AbortSignal fired on cancellation or timeout |
| How the handler ends | Task becomes |
|---|---|
return value |
completed. The value must be JSON |
throw |
failed. The message (with cause), tokens masked, cut at 4,000 characters. The stack stays in the local log |
| Timeout | failed (timeout) |
| Cancelled | ctx.signal fires; the run is ended 2 seconds later. Its return value is not sent |
- Apply changes: edits to a handler's code and to
.envapply from the next task. Adding or renaming a file, or changing a description, needs3minapi reload(or the nextstart);worker newreloads by itself - Removing a file does not remove the worker from 3Min API — its tasks fail. Use
3minapi worker delete <name> - Secrets (your own API keys) go in the project's
.env, which.gitignorealready covers. Save it as UTF-8 (UTF-16 is rejected) - The same task can arrive twice (at-least-once). A worker with side effects should tolerate a repeat
- A result whose top level has a
_3minapikey fails withreserved_result_key - Your worker can keep its own data in
data.db: open it withnode:sqliteatnew URL('../data.db', import.meta.url). Tables starting with_3minapi_are the CLI's — read only
Running from your AI
Once start is running, ask your AI as usual — no tool names needed.
- "Rename the photos in D:\Photos\2026 by date"
- "Check the BTC price every minute, 10 times"
- "Stop the price check"
The AI uses worker_list → task_create → task_get. A task waits on 3Min API until the CLI fetches it — a running CLI in live mode (the default) is signalled and fetches it, one in poll mode at its next check. task_get shows delivery: "pending" until then, "delivered" after.
Repeating tasks
For local workers only, task_create takes repeat to run the same input on a fixed interval — one task, one task id for every run.
{
"worker": "price-feed",
"input": { "symbol": "BTC" },
"repeat": { "interval_seconds": 60, "max_runs": 10 }
}
| Field / rule | Value |
|---|---|
interval_seconds |
10 to 604,800 (7 days). A run still going when the next is due skips that one |
max_runs |
Optional. Leave it out and it repeats until cancelled — stop it by asking the AI to cancel |
| Status | Stays working; repeat shows runs, failed and last_run_at. completed after the last run, with a summary |
| Failures | A failed run does not stop it. 5 failed runs in a row → failed (repeat_failed) |
| Cost | Each run is 1 call, besides what the CLI spends to receive tasks. Every minute without an end ≈ 43,200 calls a month |
| Changing it | Cancel and create a new one |
| While the CLI is off | Paused. No run reported for 24 hours plus one interval → failed |
| Public worker | Not allowed — repeatRequiresLocalWorker |
Each run's result is saved on your PC as its own row (run_no 1, 2, …).
Reading results
Every result is saved in data.db on your PC. Results up to about 100 KB are also sent to 3Min API and reach the AI through task_get. A larger one is not sent: the task completes with a substitute in result._3minapi (stored_locally: true, task id, size, a shape summary and the reader name).
AI — through the host's results reader, a built-in local worker named _results_ + 8 characters of the host id. worker_list lists it as results_reader of that host in hosts, not among the workers. It computes on your PC and sends back only the answer. The task_result_* tools below hand each step to it and pick the PC from the task_id or worker given, so the AI does not need its name.
| Order | Tool | Use |
|---|---|---|
| 1 | task_result_describe (task_id or worker) |
Learn the shape first. A path a result lacks reads as NULL, so mixed shapes give wrong sums |
| 2 | task_result_query (sql, worker) |
Read-only SQL over the view results. Aggregate and return only the answer |
| 3 | task_result_read (task_id, path, offset) |
Raw pieces of about 90 KB, only when the raw data itself is needed |
- Each reader task costs at least 4 calls and waits until the CLI fetches it
- The reader is registered when
startruns with at least one worker file
You — 3minapi worker results list/show/describe/query or 3minapi dashboard. data.db is never cleaned up automatically; use worker results prune. It may contain personal data.
Letting an AI run the CLI
Some AI clients can run commands on your PC once you allow it — coding agents such as Claude Code or Codex, and some desktop apps. Others, such as ChatGPT on the web, cannot: run the command yourself and paste back what it printed.
| The AI can | The AI should not touch |
|---|---|
Edit workers/*.mjs (description, handler) and 3minapi.json |
The host token — never in a chat or on a command line (3minapi login prompts) |
Run worker new, worker run, reload, status, doctor, logs, worker results … |
The CLI's own folders (credentials.json, control.db) — see 3minapi paths |
Run init <folder> or init --yes after asking where |
Tables starting with _3minapi_ in data.db |
- Without a terminal, destructive commands need
--yes, so--yesis the only confirmation. Confirm with the user beforeworker delete --yes— it deletes the worker and its task history --jsongives output an AI can parse. Errors come as{ "error": { "kind", "message", "exitCode", "code"?, "resetAt"? } }.resetAtcomes with a quota stop when the reset time is known
Hosts and tokens
| Situation | What to do |
|---|---|
| Several PCs | Add one host per PC. One PC = one host = one project. Worker names must differ across PCs |
| Token lost or leaked | Rotate it on the host page. The old one stops at once, and a CLI using it stops |
| Moving to another PC | Rotate the token → copy the project folder → 3minapi login with the new token → 3minapi start. Workers and history stay |
| Deleting a host | Deletes its local workers and their task history. Use it only to start clean; a running CLI stops |
| Moving the folder | 3minapi stop → move it → run a command inside it, or 3minapi init <folder> --move |
The host page shows how the CLI receives tasks (live or polling), when it last connected or checked in, the check interval (polling only), concurrency, version and platform — facts reported by the CLI, not a current status.
Limits and quota
| Item | Value |
|---|---|
| Local workers | Unlimited on every plan |
| Receiving (live, default) | Keeping the connection costs no calls. 1 per connect or reconnect plus 1 catch-up fetch after it, and 1 per fetch when a task or a cancellation is signalled. The connection drops on the network side a few times a day and reconnects on its own, so even with no work it uses some calls — 6 drops a day ≈ 12 calls a day ≈ 360 a month |
| Receiving (polling) | Every fetch is 1 call, even when nothing is waiting. Default every 60 seconds ≈ 1,440 a day ≈ 43,200 a month |
| Other calls | 1 per receipt confirmation, 1 per report (ctx.report and the final result), 1 per repeat run, 1 per reload/status |
| Concurrent runs | concurrency, default 4 (1–50). At most concurrency tasks per fetch |
| Deadline | Not picked up within 24 hours of creation → failed (checked hourly, up to about 25 hours) |
| Task history | At least 60 days on paid plans. On Free, local workers and their runs are deleted 7 days after the worker was registered; reload or the next start registers it again |
| Quota used up | The CLI stops itself (exit code 5) and does not restart. Reports are never blocked |
| Result sent to 3Min API | About 100 KB; larger results stay on your PC (substitute sent) |
| Local logs | Kept 30 days / 300 MB by default (logRetentionDays, logMaxBytes) |
To spend fewer calls, keep the default live mode. If your network needs polling, raise pollIntervalMs in 3minapi.json (tasks are picked up later) and restart.
While all concurrency slots are busy, the CLI takes no new tasks — they stay pending (cancellations still arrive). 3minapi start and 3minapi status show the resulting throughput.
Status and troubleshooting
Is it running? Check with 3minapi status on that PC. CLI receiving on the web, and the last check or connection worker_list gives the AI, are facts about the past, not a current status.
| Symptom | Cause and fix |
|---|---|
Task stays working with delivery: "pending" (Waiting for PC on the web) |
The CLI has not fetched it. Run 3minapi start on that PC |
3minapi start says it could not open the live connection |
A company proxy or firewall blocks it. 3minapi doctor checks; start with --mode poll (or set receiveMode: "poll") to fetch on an interval instead |
Not picked up within 24 hours — the 3Min API CLI on the user's PC did not fetch it. |
The CLI was off for 24 hours. Start it and send the task again |
No run was reported for 24 hours — the 3Min API CLI on the user's PC stopped running it. |
A repeating task lost its CLI. Start it and create the task again |
Worker did not respond within 24 hours. |
A local task the CLI took but never finished reporting — the PC stayed off after a crash. It is not run again |
Failed with host_stopped |
You stopped the CLI (Ctrl+C or stop) while it ran. A repeating task only loses that run |
Failed with host_restarted |
The PC or CLI died mid-task. It may or may not have run, and it is not run again |
| AI sends input in an old shape | You changed a description without 3minapi reload |
Worker missing from worker_list |
New file without reload, a load error (3minapi worker list), or Free 7-day deletion (reload) |
CLI refuses a name with worker_name_taken |
Another worker (public, or on another PC) has that name. Rename the file |
| CLI exited with code 4 or 5 | 4: token invalid or no plan — login again. 5: monthly quota used up |
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | Failure |
| 2 | Usage error (including a missing --yes) |
| 3 | Environment or config error |
| 4 | Stopped: token invalid, or the account has no plan (Free counts as a plan) |
| 5 | Stopped: monthly quota used up |
| 130 | Interrupted (Ctrl+C) |
After 4 or 5 nothing restarts on its own.
error.data.reason of a failed local task
| Reason | Meaning |
|---|---|
handler_error |
The handler threw |
timeout |
Ran past timeoutMs |
invalid_input |
Did not match inputSchema — not run |
invalid_worker |
The file exists but cannot be used — no handler, a duplicate name, or an import error |
worker_not_found |
No such file on that PC |
unsupported_syntax |
TypeScript the CLI cannot run without a build (e.g. enum) |
result_too_large |
Over about 100 KB and it could not be saved on the PC |
reserved_result_key |
The result has a top-level _3minapi key |
host_stopped |
The CLI was stopped mid-run |
host_restarted |
The PC or CLI died mid-run; reported on the next start |
repeat_failed |
A repeating task failed 5 runs in a row |
Protocol summary
How the CLI and 3Min API work together, for reference.
Delivery
- Pull: in live mode the CLI keeps a connection open and asks 3Min API for waiting tasks when it is signalled (and once after each connect); in poll mode it asks every
pollIntervalMs. Either way it takes only as many as it can run now - Ack: it confirms receipt right away (
deliverybecomes"delivered"). Unconfirmed for 60 seconds, the task goes out again — up to 6 times, thenfailed - At-least-once: the same task can arrive twice
- Cancel: signalled in live mode, or announced on the next fetch in poll mode →
ctx.signal. The task stayscancelled - Reports: progress and the final result go back with the host token. A report that fails on the network is retried for up to 24 hours
Sizes
| Item | Limit |
|---|---|
input |
100 KB |
| Result sent | About 100 KB (100 KiB minus a 1 KiB envelope) |
ctx.report |
256 characters |
| Error message | 4,000 characters |
| Worker description | 500 characters |
| Worker name | 50 characters |
3minapi.json (precedence: flag > environment variable > project file > default)
| Key | Default | Range | Environment variable |
|---|---|---|---|
workersDir |
workers |
Inside the project | — |
receiveMode |
live |
live or poll |
THREEMINAPI_RECEIVE_MODE |
pollIntervalMs |
60000 | 5000–3600000 (poll mode only) | THREEMINAPI_POLL_INTERVAL_MS |
concurrency |
4 | 1–50 | THREEMINAPI_CONCURRENCY |
localServerPort |
3180 | 1024–65535 | THREEMINAPI_LOCAL_SERVER_PORT |
logRetentionDays |
30 | 1–365 | THREEMINAPI_LOG_RETENTION_DAYS |
logMaxBytes |
300 MB | 10 MB–1 GB | THREEMINAPI_LOG_MAX_BYTES |
Changing receiveMode, pollIntervalMs or concurrency needs a restart. Unknown keys only warn.
Folders — 3minapi paths prints them.
| OS | Token and settings | Control DB and logs |
|---|---|---|
| macOS | ~/Library/Application Support/3minapi |
~/Library/Application Support/3minapi, ~/Library/Logs/3minapi |
| Windows | %APPDATA%\3minapi |
%LOCALAPPDATA%\3minapi |
| Linux | $XDG_CONFIG_HOME/3minapi (~/.config) |
$XDG_STATE_HOME/3minapi (~/.local/state) |
The token lives in these folders, not in the project, so copying the project does not copy the token.
Privacy
- A worker's
resultanderrorreach the AI conversation as is. Return only what is needed data.dbkeeps every result on your PC, including any personal data in it, until you prune it