runtm-api is a small Go CLI that wraps the Runtime Cloud API. It is intentionally separate from the pip runtm CLI (which handles local sandboxes, scaffolding, and deploys):
Most humans use both: the pip CLI for day-to-day work, the Go CLI when an agent needs to act on the hosted platform programmatically.
Install
runtm-api to /usr/local/bin. If Claude Code or Cursor is detected, the bundled agent skill is auto-installed too.
Install a specific version
Install into a directory you own (no sudo prompt)
Installer environment variables
Install via go install (Go developers)
If you already have Go 1.23+ and prefer to compile from source:
Auth and config
The same API key works for both this CLI and the pip CLI.
Commands
Run
runtm-api --help or runtm-api <command> --help for full flag reference.
Running commands in a sandbox
session exec prints the raw PTY stream by default, which merges stderr into stdout and carries the sandbox shell’s startup banners. Pass --json whenever the output will be parsed:
set -e capture the output with || true and read exit_code instead.
Bash history expansion is disabled for the command in both modes, so a literal ! in a heredoc, commit message, or regex reaches the sandbox intact. A paused sandbox is resumed automatically by exec, connect, and the file commands.
Output contract and exit codes
- Stdout is always JSON. Parse it directly; never scrape the PTY stream (see
--jsonabove). - Stderr carries structured error JSON:
{"error": "...", "status": 401, "hint": "..."}. - Streaming commands (
session prompt,session events,session deploy run,template build-logs) emit JSON lines, one per server event:{"event": "<type>", "data": <payload>}. The stream ends with aneventofdone. - Exit codes:
Org context
Org-scoped operations (templates, agents, scheduled agents, skills, MCP servers, tools, guardrails, team secrets, org instructions, team telemetry) need an org-scoped API key. The org is bound to the key when it is created and cannot be changed at call time.--org and RUNTM_ORG_ID can only restate the key’s own binding. If a command reports the key is personal, create an org-scoped key under Settings > API Keys; setting the env var does nothing.
Resolving required inputs
- Check context first: prior command output, the conversation, environment variables.
- If a value is missing, run the matching
listorget:session listfor a session id,template listfor a template id,agents listfor an agent id,agents list --type slackfor a Slack integration id. - If it is still ambiguous, ask the user.
- Never run a command with an unresolved placeholder such as
<id>or<org>.
Endpoint strategy
Everything goes to the Cloud API athttps://app.runtm.com/api/cloud. Three deliberate /api/v0/ fallbacks remain because they were built for fire-and-forget agent use:
deployments list|get|logs|destroy also proxies /api/v0/deployments*, so a deployment a session shipped can be tracked and torn down without the pip CLI.
Error recovery
Which command carries which capability
Everything an agent can do hangs off one template. The roster row carries identity; the template carries capability. Plan the list before creating anything, and check what the org already has.Assembly order and the failures that are silent
Create the template first, attach everything, verify, build once, then the roster agent, then the trigger. The Build pages explain why; this is the CLI shape.template create --skip-agent implies --build: the clone-only fast path builds immediately, so anything attached afterwards needs a rebuild. Batch attachments and build once; rebuilding after every attach churns the snapshot.
These fail with no error, only an agent that cannot do its job:
Prove a capability landed before wiring the trigger:
Roster versus triggers
agents with no --type is the roster (identity, instructions, rubric, budget). --type slack|github|linear|email is a trigger integration. Same verbs, different resource.
updateis a partial patch: only the flags you pass change.--config '<json>'merges into the trigger’s config server-side, so a one-key patch never clobberstriggersorchannel_template_map.--modelis shorthand forconfig.default_model.--clear-templatesends an explicit null and clears the roster’s default template, fanning out to every linked trigger. Omitting--templateleaves it unchanged.- The rubric’s version bumps on every edit, and every grade records the
criteria_versionthat produced it. - Email has no get-by-id route;
agents get <id> --type emailis resolved from the list. - Deleting a roster row while a trigger still references it is not durable: the trigger’s lazy sync recreates the row. Delete or reassign triggers first.
Trigger preflight
Check these beforeagents create --type ... so you fail with an explanation instead of a bare 400:
- Slack needs the org’s Slack app configuration token, set once in the dashboard. There is no CLI command for it. Create returns an
authorize_urlto open; the trigger is created on approval. - GitHub cannot be a clickable link. The App-manifest flow requires a form POST, so the CLI writes an auto-submitting HTML page (
openin the response) for you to open in a browser.--github-orgpicks the owner,--return-urlwhere GitHub sends you back. - Linear is headless only with
--linear-api-keyplus--service-user(the Runtime user id the bot’s sessions run as). Managed OAuth returns an install URL to open. - Email is fully headless and provisions an inbox.
--agent-idbinds it to a roster agent so runs inherit defaults and rubric. If it returns400, checkGET /api/v1/email/status.
Scheduled agents from the CLI
Aliases:scheduled-agents also answers to schedules and cron; run-now also answers to run and trigger.
- The order that works is create
--disabled,run-now, read the session, thenupdate --enabled.run-nowworks on disabled agents and stampslast_run_atandlast_session_idlike any tick. - Cron is five fields in UTC with no per-agent time zone. Daily 11:00 Pacific is
0 19 * * *in winter and0 18 * * *in summer; re-derive it when daylight saving shifts. next_run_atis null while the agent is disabled, because a disabled agent has no scheduler job.--slack-integrationand--slack-channeltravel as a pair; one without the other is rejected. Find integration ids withagents list --type slack. Runtime posts the result after the run; the sandbox does not need Slack egress.- The prompt decides the output shape. A prompt that ends “post the Block Kit JSON” pastes raw JSON into the channel; ask for a short plain-text summary when a person reads it.
- Pause with
--disabledrather than delete: the scheduler job goes away, the prompt, template and post target stay. - “It was supposed to run and nothing happened”, in order:
get <id>and checkenabledandnext_run_at; comparelast_run_atwith the expected time (unchanged means the tick never fired, updated means the failure is inside the run);run-nowto reproduce, a502carries the reason;session history <last_session_id>.
Subcommand discovery
The agent skill
The CLI embeds oneSKILL.md (packages/agent/skills/) and installs it into ~/.claude/skills/runtm/ and ~/.cursor/skills/runtm/ when those directories exist. The installer does this automatically; re-run it any time with:
https://docs.runtm.com/<page>.md, and lifts the cli: lines that every Build and Guides step carries in an agent-only block (hidden on the website, present at the .md URL). A cli-handoff: comment marks a step only a person can do, usually entering a credential: the agent sends the person that URL (with the placeholders filled in), then runs the step’s cli-verify: to confirm it was done. Inside a sandbox the same content is reachable through the sandbox’s fetch tool or curl.
Source
The CLI lives atpackages/agent/ in the open-source runtm-ai/runtm repository. Releases are cut by pushing a packages/agent/vX.Y.Z tag, which triggers a GitHub Actions workflow that publishes pre-built binaries to GitHub Releases.
Apache-2.0 licensed (same as the pip CLI).