{"name":"TechTools Agents API","version":"1","description":"Create AI agents that work on their own in a private Linux computer: each agent has a name, a goal and a soul (how it thinks, Markdown). Agents run on OpenAI models (Codex harness) or DeepSeek models (Claude Code harness) and are paid from the account credit, metered per token at the prices of GET /prices. Each account can get a starter credit once (POST /starter): with its own @techtools.cz mailbox, or with an e-mail address linked to the account (/shared-auth-api/linked-email) that has not earned a starter credit before.","base_url":"https://techtools.cz/agents-api","tool_page":"https://techtools.cz/tools/agents/","auth":"TechTools shared-account session cookie (_techtools_session), see /shared-auth-api/. Every endpoint except /docs and /pulse needs it (401 AUTH_REQUIRED). Send credentials with every request. An agent is visible only to its owner; another account's id answers 404.","conventions":{"format":"JSON in and out (Content-Type: application/json on every request with a body). Request bodies above 64 KiB are refused (413 too_large). A changing request from another origin (Origin or Sec-Fetch-Site not this site) or with a non-JSON body is refused (403 forbidden).","money":"Integer micro-USD in every field ending in _micro (1 USD = 1,000,000). Prices are micro-USD per 1M tokens. Never a float.","time":"ISO 8601, UTC.","errors":"{ \"error\": \"\u003cEnglish sentence\u003e\", \"code\": \"\u003ccode\u003e\", ...details }. Map the code, not the sentence.","status_codes":"200 read/update, 201 created (nothing started), 202 accepted (the agent is being started, stopped or messaged), 400 malformed, 401 not signed in, 402 credit, 403 suspended, 404, 409 state conflict, 413 too large, 422 validation, 429 rate limited (Retry-After).","rate_limits":"60 changing requests per minute per account; creating agents 10 per hour; messages 20 per minute per agent; starts 12 per hour per agent and 30 per hour per account; credit requests 5 per day per account and 10 per day per network; starter credit 10 tries per 10 minutes per account; GET /agents/:id/events 600 per minute, of which 120 with a limit above 20. A page of events also stops at 512 KiB. GET /pulse is not limited.","live_updates":"Poll GET /pulse every few seconds; each agent row carries rev, which changes whenever anything shown about the agent changes."},"states":{"agent":["starting","working","idle","stopping","stopped","paused","error","deleting"],"meaning":"starting: its computer is booting; working: a run is in progress; idle: done for now, waiting for a message (stops by itself after the idle time); stopped: computer off, files kept; paused: cannot run until something changes (reason no_credit, suspended_agent, suspended_user, disabled); error: the last start or run failed (reason_text says why)."},"schedules":{"what":"An agent can run its goal again on a schedule. Each tick is a new run (trigger scheduled) in a fresh session that sees the files of earlier runs in ~/work. Your messages keep going to the current session.","fields":"schedule_every_min (null = Once, or minutes), schedule_effective_every_min (the interval it runs at: the saved one, raised to the admin minimum when that was raised later), schedule_on (false = paused), next_run_at, last_scheduled_at, schedule_status (ok, skipped_busy: a run was still going; skipped_capacity: no room, plan limit, free computer time or a run that could not be delivered, see schedule_detail; paused_credit, paused_suspended, paused_disabled: resumes by itself when that clears; paused_failed: 3 scheduled runs in a row could not be delivered, the schedule is off until you resume it), schedule_status_at, schedule_detail (the error code behind a skip or pause).","rules":"No overlap (a tick while a run is queued or working is skipped). Ticks are anchored: the next one is the previous due time plus the interval, and missed ticks are never run in a burst. Intervals up to 15 minutes keep the computer on between runs; longer ones let it stop when idle and start it again. Every run is charged like any other run."},"endpoints":{"pulse":{"method":"GET","path":"/pulse","auth":"optional","returns":"Always 200. Signed out: { v, authenticated: false, enabled, service }. Signed in: service {on (the service switch), scope: everyone | admins} says why enabled is false, balance_micro, held_micro, free_micro, verified, suspended, capacity_ok, plan {name, running_max, total_max}, request (your newest credit request), enabled, and agents[] (id, rev, state, reason, name, model_id, model_label, spent_micro, run_spent_micro, last_line, last_at, state_at, idle_stops_at, stale_days, capacity_ok (this agent's own size fits the host now), and the schedule fields schedule_every_min, schedule_effective_every_min, schedule_on, next_run_at, last_scheduled_at, schedule_status, schedule_status_at, schedule_detail). The top-level capacity_ok says whether the smallest computer you can start fits; for an admin, capacity_ok_sizes[] says it per listed computer size (the size_index order of /me sizes)."},"me":{"method":"GET","path":"/me","returns":"Account view: user, verified (the account had its starter credit), starter { amount_micro, granted, available, via: mailbox | linked_email | null, reason: the error code POST /starter would answer now, or null }, balance, plan with computer size, enabled, terms_accepted, soul_templates [{key, title_en, title_cs, text}], settings {idle_stop_min, stale_warn_days}, schedule {presets (the intervals in minutes you may pick), min_interval_min}, runtime (accounts with no paid credit left: {used_min, day_max_min, session_max_min} of container time today, else null), terms_url."},"prices":{"method":"GET","path":"/prices","returns":"{ models: [{ id, slug, label, tagline, provider, visibility, is_default, price_in_micro, price_cached_micro, price_write_micro, price_out_micro, min_balance_micro, typical_run_micro, long_context }], typical_run: {cached_in, new_in, out}, checked_at }","note":"typical_run_micro = ceil((cached_in*price_cached + new_in*(price_write or price_in) + out*price_out) / 1e6). A call above long_context.threshold input tokens is billed at in_pct/out_pct percent. min_balance_micro = the available credit a start needs: the model's minimum, and never less than the smallest real call the gateway grants (8192 input tokens at the higher of price_in and price_write plus 1024 output tokens at price_out); below it a start is refused and a running agent pauses. An agent whose last run the gateway ended no_credit also pauses (no_credit) while the balance is below what that call needed, until credit is added or the balance covers it again."},"create_agent":{"method":"POST","path":"/agents","body":{"name":"1..60 characters, unique among your agents","goal":"1..8000 characters","soul":"optional Markdown, up to 32000 characters; empty = the template default","soul_template":"general | builder | researcher | watcher | custom","model_id":"id of an enabled model from GET /prices","start":"true to start at once, false to save for later","schedule_every_min":"optional: null = Once (default), or run the goal again every 5, 15, 30, 60, 240, 720 or 1440 minutes (GET /me schedule.presets)","schedule_on":"optional: false saves the interval paused (default true with an interval)","accept_terms":"optional true (records acceptance of the Terms of use)"},"returns":"202 { agent } when starting; 201 { agent, start_error? } when saved or when the start was refused (the agent is still created).","example":{"name":"Bakery site","goal":"Build a one-page website for a small bakery in ~/site.","soul_template":"builder","model_id":1,"start":true}},"get_agent":{"method":"GET","path":"/agents/:id","returns":"{ agent }: state, reason, model, goal, soul, size, spent_micro, run (the open run), queued messages, last_line, idle_stops_at, stale_days, suspended, last_error, setup_rev, setup_applied_rev, spent_d30_micro, runs_count, rev, the schedule fields (see schedules) and schedule_cost_day_micro (runs per day x the model's typical run, an estimate; null for Once)."},"update_agent":{"method":"PATCH","path":"/agents/:id","body":"any of { name, goal, soul, soul_template, model_id, schedule_every_min, schedule_on } (the model must stay with the same provider, and changes only while the agent is not running: 409 invalid_state with field model_id otherwise; schedule_on false pauses the schedule, true resumes it)","returns":"{ agent }. Changes apply from the next run."},"delete_agent":{"method":"DELETE","path":"/agents/:id","body":{"confirm_name":"the exact agent name"},"returns":"202 { agent } (state deleting). Files are lost; runs and charges stay in the wallet."},"start_agent":{"method":"POST","path":"/agents/:id/start","body":{"replace_agent_id":"optional: the working agent to stop first when your plan allows only one at a time"},"returns":"202 { agent }","refusals":"disabled, suspended, no_credit, min_balance, runtime_limit {day_max_min}, model_unavailable, plan_running {busy}, capacity, invalid_state, rate_limited (start budget)"},"stop_agent":{"method":"POST","path":"/agents/:id/stop","returns":"202 { agent } (stopping), 200 if already stopped. Stop also pauses the schedule (schedule_on false); Start does not turn it back on."},"send_message":{"method":"POST","path":"/agents/:id/messages","body":{"text":"1..8000 characters","send_now":"optional true: interrupt the current run"},"returns":"202 { message, agent }. A busy agent queues it (up to 5 waiting) and reads all waiting messages together in its next run; a stopped agent is started."},"message_send_now":{"method":"POST","path":"/agents/:id/messages/:message_id/send_now","returns":"202 { message, agent }"},"cancel_message":{"method":"DELETE","path":"/agents/:id/messages/:message_id","returns":"200 { message } (cancelled); 409 not_queued when already delivered"},"events":{"method":"GET","path":"/agents/:id/events","params":{"after":"event id: newer events, ascending","before":"event id: an older page","limit":"1..200, default 200"},"returns":"{ events: [{ id, run, at, kind, title, body, status, meta }], goal, last_id, has_more_before, has_more_after } (a page stops at 512 KiB: continue with after=\u003clast id\u003e while has_more_after)","kinds":"goal, run_start, you, agent, step, plan, error, run_end, sys"},"receipt":{"method":"GET","path":"/agents/:id/runs/:seq","returns":"{ run: { seq, trigger, state, end_reason, stop_reason, started_at, ended_at, duration_s, calls, pending_calls, failed_calls, spent_micro, lines: [{ model_label, class, tokens, price_micro, charge_micro }], rounding_micro, capped_micro, balance_after_micro } }"},"wallet":{"method":"GET","path":"/wallet","params":{"before":"cursor from next_cursor","limit":"1..100, default 30"},"returns":"{ balance_micro, held_micro, available_micro, free_micro, spent_today_micro, spent_d30_micro, items: [run charges, credits, and { type: \"usage\" } items for calls charged outside any run (one per agent and UTC day), newest first], next_cursor }"},"credit_request":{"method":"POST","path":"/credit-requests","body":{"kind":"credit | review","note":"optional, up to 500 characters"},"returns":"201 { request }; 409 request_open when one is already open, 409 disabled while Agents is switched off for the account"},"starter":{"method":"POST","path":"/starter","body":{"accept_terms":true},"returns":"200 { verified: true, granted_micro, balance_micro, via: \"mailbox\" | \"linked_email\" }","note":"The starter credit, once per account, ever, with no code. An account with its own @techtools.cz mailbox gets it with no further step, as long as that mailbox address has not earned one before. Otherwise the account links an e-mail address to the TechTools account (POST /shared-auth-api/linked-email and /confirm, a 6-digit code), then sends this request; each address earns one starter credit, ever, across all accounts. Both kinds of grant have daily budgets and answer 429 retry_after (with a Retry-After header) over them: mailbox grants at most 15 per 24 hours per IPv4 address or IPv6 /48; linked-address grants per network, domain and globally. Refusals: 409 starter_off (grants are switched off), already_verified (this account had its credit), email_required (no mailbox and no linked address), disabled; 422 terms_required, email_not_eligible (the address earned one before); 403 suspended. Agents stores no address, only keyed digests."}},"error_codes":["AUTH_REQUIRED","forbidden","not_found","too_large","rate_limited","invalid","name_taken","confirm_mismatch","model_provider_change","size_admin_only","invalid_state","queue_full","not_queued","no_credit","min_balance","suspended","disabled","model_unavailable","plan_running","plan_total","capacity","runtime_limit","request_open","already_verified","starter_off","email_required","email_not_eligible","retry_after","terms_required"],"examples":["curl -s https://techtools.cz/agents-api/prices -b cookies.txt","curl -s -X POST https://techtools.cz/agents-api/agents -b cookies.txt -H \"Content-Type: application/json\" -d '{\"name\":\"Bakery site\",\"goal\":\"Build a one-page site in ~/site.\",\"model_id\":1,\"start\":true}'","curl -s \"https://techtools.cz/agents-api/agents/42/events?after=1880\" -b cookies.txt"],"limits":"Agents have internet access but no inbound access and cannot send e-mail. Files stay while an agent is stopped and are removed when it is deleted. Credit is non-refundable. Deleting your Agents data in My data (or closing the account) removes the agents and their computers and forfeits the remaining credit; the money record stays, no longer linked to you."}