On this page

Machinus Fast API · v1

Prepare web forms. Submit only when you say so.

A run opens a page, fills what an end user's Memory and the context you send support, asks for the facts they do not, and stops ready for review. Nothing is submitted except by approve, and approving needs the digest of exactly what was prepared. The Fast controller combines low-latency reasoning, field classification, and a persistent browser per end user.

  • Each of your end users has a Memory and a browser profile of their own: sign-ins persist between their runs, and no other end user can read them.
  • Every filled value says where it came from (Memory, your context, an answer, a document, or the page itself).
  • Questions are data: when a fact is missing the run returns structured questions, and a person or your own AI answers them.
  • When a person has to act (a sign-in, a code), you hand them a link: no Machinus account needed.
  • There is no limit on how many runs work at once, or wait prepared for approval.

This is the Fast API (/api/fast/v1). It is separate from the Infrastructure Platform (/api/v1), which has its own docs.

Build it with an AI agent

Copy for your AI agent

Paste this into your coding agent before it writes anything. It carries what an agent needs to build the whole integration: keys and scopes, the run states, questions, approval, the mode setting, webhooks and cost.

Machinus Fast API integration prompt

You are integrating the Machinus Fast API into a product. The reference is https://machinus.xyz/docs/fast Base URL: https://machinus.xyz/api/fast/v1. Follow these rules.

AUTHENTICATION
1. Authenticate server-to-server with a key that starts with mf_live_: "Authorization: Bearer mf_live_...". A key belongs to one organization and carries scopes. It is shown once. Keep it in a server secret store. Never put it in a browser, a mobile app, a client bundle, a prompt, or a log.
2. Ask for the fewest scopes. Creating and reading runs needs runs:write and runs:read. runs:approve is the only scope that can submit anything: give it only to a key whose job is to finish runs. A missing scope is 403, a bad key is 401.

WHAT A RUN IS
3. POST /runs starts one browser task and returns at once with {"id","status":"queued"}. Body: url (a public http or https address), goal (up to 4000 characters, say what to do and when to stop), account (your own id for the end user, so the run uses that user's Memory and browser profile), optional context {facts:[{label,value}], notes}, field_map (form label to your own key, read back in /runs/{id}/output), metadata (returned on the run and on every event), and mode.
4. Send an Idempotency-Key on every POST /runs. A repeated key returns the original run; the same key with a different body is idempotency_conflict (409). Never retry a write without one.
5. mode says what the run does at the end. "prepare" (the default) fills the form, stops in front of the final control, and the run becomes ready: nothing is submitted until you call approve. "complete" also presses the final control itself, once, when the run's safeguards pass. mode "complete" needs a key with runs:approve (403 otherwise). A complete run that fails a safeguard stops as ready like a prepare run, and its completion.held says why; you can still approve it. Use complete only for destinations you trust, and read the run's mode and completion fields to know what happened.

STATES (GET /runs/{id}, until it needs you or ends; or use a webhook)
6. queued, running: wait.
   needs_input: facts are missing. "questions" lists them as data. Answer with POST /runs/{id}/answers {"answers":[{"question_id","value"}]}; a choice question takes one of its listed options. Ask your own user or answer from your own data. Never invent a value.
   needs_takeover: a person must act in the browser (a sign-in, a code, a CAPTCHA that could not be solved). Make a link with POST /runs/{id}/takeover-link, give it to the person, then POST /runs/{id}/resume.
   ready: everything is filled and checked, nothing was submitted. Show "fields" to a person (each has a source: memory, context, answer, document or page) and approve with POST /runs/{id}/approve {"preview_digest":"<the digest you were shown>"}. The digest ties the approval to exactly what was reviewed; repeating approve returns the first result. A prepared page nobody approves expires.
   submitted: the form was submitted. completed: a task that is not a form finished; read GET /runs/{id}/output. failed, cancelled, expired: terminal; "error" says why. POST /runs/{id}/cancel stops a run that was not submitted.
7. GET /runs/{id}/output returns what was answered by your own keys and what is still unanswered. GET /runs/{id}/actions lists what the agent and the person did, in order, including an auto_approved entry from "system" when a complete run approved itself.
8. Passwords and sign-in codes are never typed by the agent. A field that holds a secret comes back empty and marked redacted. Save logins an agent may use with the credentials API; it can never read one back.

END USERS AND MEMORY
9. Each end user has their own Memory and browser profile. Create or update them with the accounts endpoints, save facts and documents so runs can fill from them, and pass the same account id on every run for that user. Memory is read by the agent and never written by it during a run.

STAYING INFORMED
10. Prefer webhooks to polling: POST /webhooks returns a signing secret once. Each delivery carries "Machinus-Signature: t=<unix seconds>,v1=<hex>", where v1 is the HMAC-SHA256, keyed with the secret, of "<t>.<raw body>". Verify it on the raw body with a constant-time compare, reject old timestamps, and ignore an event id you have already handled (delivery is at least once; any 2xx is success). A complete run passes through ready for an instant: a client that acts on ready should check mode and completion first.

COST AND LIMITS
11. GET /usage and GET /billing report what runs used and were charged; read them, do not estimate. Runs queue without limit. Lists take limit (up to 100) and return next_cursor. Errors always have the shape {"error":{"code","message","details"}}.

BEFORE WRITING CODE
State which key scopes, which mode, which account id, how you will answer questions, who approves and how, and how webhooks are verified. If the documentation and a requested behavior conflict, follow the documented contract and say so.

Start here

Prepare a form in four calls

Keys are issued from a signed-in session (below). Then, from your server:

# 1. Start a run for one of your users. It returns at once.
curl -X POST https://machinus.xyz/api/fast/v1/runs \
  -H "Authorization: Bearer mf_live_..." \
  -H "Idempotency-Key: apply-2026-0001" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://forms.example.com/apply","goal":"Prepare my application.","account":"user_123"}'
# -> {"id":"run_k17abc","status":"queued"}

# 2. Read it until it needs you, or is ready.
curl https://machinus.xyz/api/fast/v1/runs/run_k17abc -H "Authorization: Bearer mf_live_..."
# status "needs_input": answer the questions
curl -X POST https://machinus.xyz/api/fast/v1/runs/run_k17abc/answers -H "Authorization: Bearer mf_live_..." \
  -d '{"answers":[{"question_id":"q_1","value":"MVP"}]}'

# 3. status "ready": review fields, then approve with the digest you were shown.
curl -X POST https://machinus.xyz/api/fast/v1/runs/run_k17abc/approve -H "Authorization: Bearer mf_live_..." \
  -d '{"preview_digest":"sha256:..."}'
# -> {"id":"run_k17abc","status":"running"}  then "submitted"

Instead of polling, register a webhook and be told when a run needs you or is ready. account is your own id for the end user; the run then uses that user's Memory and browser profile.

Authentication

Keys and scopes

Send Authorization: Bearer mf_live_... on every call. A key belongs to one organization and carries scopes. Only a hash is stored; the key is shown once. A call with a missing, revoked or expired key is 401; a missing scope is 403.

# Issue a key (needs your signed-in Machinus session; the key is shown once).
POST /api/fast/keys   {"name":"Production","scopes":["runs:write","runs:read"],"org_id":"..."}
GET  /api/fast/keys              # your keys, never their secrets
DELETE /api/fast/keys?id=...     # revoke
ScopeWhat it allows
runs:writeCreate, answer, resume, cancel and automate runs; make takeover links.
runs:readRead runs, their output and their actions.
runs:approveApprove a prepared run. This is the only scope that can submit anything.
accounts:writeMake, change and delete end users; their Memory, documents, records, consent, portal links and browser profile.
accounts:readRead end users and what is kept for them; search their Memory.
credentials:manageSave, list and remove the logins an end user's agent signs in with. It can never read one back.
connections:manageStart, list and remove the apps (Gmail, Slack, Notion...) an end user connects for the agent to read. It can never read what an app holds.
search:readSearch the public web (POST /search). Each call is charged on its own.
fetch:readRead public pages by address (POST /fetch). Each call is charged on its own.
events:readRead the event log.
usage:readRead usage and billing.
webhooks:manageMake, change and remove webhooks and read their deliveries.
organization:manageRead and change your organization's settings, and delete it.
organizations:manageAn operator's: make organizations for your own customers, issue them keys, read their usage, delete them. Never given to a customer's key. (operator only)

Conventions

Requests, ids and errors

  • JSON in and out. Times are ISO 8601 UTC. Ids are prefixed: run_, q_, doc_, evt_, auto_, org_, del_.
  • Your end-user id is yours: 1 to 180 characters of letters, digits and _ . : @ + = ~ - (an email or a UUID fits).
  • Idempotency-Key on POST /runs and POST /automations/{id}/runs: a repeated key returns the original run; the same key with a different request is idempotency_conflict.
  • Lists take limit (up to 100); those that can continue return next_cursor. Fields are only ever added inside /v1.
  • Every error has one shape: {"error":{"code","message","details"}}.
CodeStatusMeaning
invalid_request400The request is malformed or breaks a rule (a field, a size, an id).
unauthorized401No key, or an unknown, revoked or expired one.
forbidden403The key lacks the scope this call needs.
run_not_found404No such run for this key's organization.
account_not_found404No such end user. Make it first, or use a key with accounts:write.
webhook_not_found404No such webhook.
organization_not_found404No such organization among those you own.
resource_not_found404No such record, document or deletion.
fact_not_found404No such fact for this end user.
automation_not_found404No such automation.
billing_required402A dedicated worker is billed to the organization, and its subscription is not active.
worker_unavailable503The dedicated worker, browser runtime, or a managed service could not do it right now. Try again.
key_not_found404No such key.
consent_required409The organization requires consent and this end user has not granted it.
webhooks_unavailable503Webhooks are not available on this deployment.
invalid_state409The run, account or organization is in a state that does not allow this (for example approving a run that is not ready).
idempotency_conflict409This Idempotency-Key was already used with a different request.
link_invalid404A takeover or portal link that is unknown.
link_expired410A takeover or portal link that has expired or ended.
internal_error500Something went wrong on our side.

The core

Browser Agent

A run goes through these states. The request body of POST /runs:

{
  "url": "https://forms.example.com/apply",     // public http(s) address; private networks are refused
  "goal": "Prepare my application.",             // up to 4000 characters
  "account": "user_123",                         // your end user (their Memory and browser profile)
  "context": { "facts": [{ "label": "Company", "value": "Acme" }], "notes": "..." },
  "field_map": { "Company name": "company" },    // form label -> your own key in output.answers_by_key
  "mode": "prepare",                             // "prepare" (default) stops ready; "complete" also presses the final control (key needs runs:approve)
  "metadata": { "ref": "abc" }                   // returned on the run and on every event
}
StatusMeaning
queuedAccepted, waiting for a worker. There is no limit on how many runs queue.
runningThe agent is working.
needs_inputFacts are missing. `questions` lists them; answer with POST /runs/{id}/answers.
needs_takeoverA person must act (sign in, a code, a CAPTCHA that could not be solved). Make them a link with POST /runs/{id}/takeover-link.
readyEverything is filled and checked; nothing was submitted. `preview_digest` is what approve must echo.
submittedYou approved and the form was submitted.
completedA task that is not a form finished.
failedThe run failed. `error` says why.
cancelledCancelled by you, or because consent was withdrawn.
expiredA prepared page nobody approved in time was discarded.
  • GET /runs/{id} shows the status, fields (each with its source: memory, context, answer, document or page), questions in needs_input, readiness and preview_digest in ready, and usage.
  • GET /runs/{id}/output is what was answered, by your own keys, plus what is still unanswered.
  • GET /runs/{id}/actions is what the agent and the person did, in order: navigation, clicks, fills, selections, uploads, answers, takeovers, CAPTCHAs solved, and the final control it left unpressed.
  • mode says what a run does at the end. prepare (the default) stops in front of the final control and waits for approve. complete presses it by itself, once, after the run's safeguards pass, and needs a key with runs:approve (403 otherwise). A complete run that fails a safeguard stops ready like a prepare run, and completion.held says why; completion.auto_approved_at shows when it approved itself, and /actions shows an auto_approved entry before the submit. The run always shows its mode.
  • approve is the call that submits, apart from a complete run, which makes it itself. It works only in ready and only with the run's current preview_digest; repeating it returns the first result.
  • A prepared page that nobody approves is discarded: the run becomes expired (expires_at says when).
  • Passwords and sign-in codes are never typed by the agent. A field that holds a secret is returned empty and marked redacted.

When a person has to act

Takeover links

In needs_takeover, POST /runs/{id}/takeover-link returns a link to a page where a person with no Machinus account sees the run's browser and uses it (sign in, enter a code), then presses "I'm done", which continues the run.

  • Shown once. Valid 15 min by default, up to 1 h, and never past the takeover itself.
  • It ends when the run leaves the takeover, when a newer link is made, or when the key that made it is revoked.
  • The person can only point, scroll and type in that page. They cannot switch tabs, and the page is served with no-store, no-referrer and frame-ancestors protections.
  • POST /runs/{id}/resume continues the run if you handled it another way.

Your users

Users

An end user is one of your own users, by your id: PUT /accounts/{id} makes or updates it. It gets a Memory of its own (a separate store) and a browser profile of its own, so sign-ins persist across its runs and never mix with another user's. A run that names an unknown account makes it on first use if the key has accounts:write.

  • Consent. PUT /accounts/{id}/consent records that the end user agreed (it is your word: you are the one who asked). With require_consent on the organization, a run for an end user who has not granted it is refused. Revoking cancels their unsubmitted runs.
  • Records. /accounts/{id}/resources keeps records about the user (a memory, document, target, data room, workflow or integration), as given, up to 500 per user. They do not change what a run does.
  • Reset. POST /accounts/{id}/browser-profile/reset clears what the user's browser learned (cookies, sign-ins, cache) and keeps everything else; their next run starts signed out. Refused while a run is in progress.
  • Delete. See Deletion.

What the agent knows

Memory and documents

  • Facts. PUT /accounts/{id}/memory saves facts (up to 50 per call, 500 per user). Saving a label again replaces its value. Facts never expire and are searchable a few seconds after they are saved. GET lists them; DELETE removes one, from the search index too.
  • Search. GET /accounts/{id}/memory/search?q=&limit= searches that user's own Memory (facts and documents), best first, up to 24 results. A service outage is an error, never an empty list.
  • Documents. POST /accounts/{id}/documents (multipart: file and optional kind of resume, pitch_deck, one_pager, company_doc, target_list; up to 15 MB) keeps a file for that user, such as a resume. It is indexed into their Memory (so it is searchable) and offered to their runs for any file field a form asks for: a run is offered the six most recent documents. Files the agent downloaded appear in the list too.
curl -X POST https://machinus.xyz/api/fast/v1/accounts/user_123/documents \
  -H "Authorization: Bearer mf_live_..." \
  -F kind=resume -F [email protected]
# -> {"id":"doc_k17abc","name":"resume.pdf","kind":"resume","status":"indexing",...}

No browser run

Search and Fetch

  • POST /search searches the public web and returns sources with highlights. Send query; optionally limit (up to 20), effort (minimal to xhigh; a higher one costs more), source_categories and an output_schema to get the answer back in your own shape. Scope search:read. A call with no effort runs at the medium level; the effort is what sets what a call costs.
  • POST /fetch reads up to 8 public pages you already know the address of, as highlights or full text, with a stable content_hash so your own scheduler can tell when a page changed. Scope fetch:read.
  • Name an end user in account and the call shows under them in GET /usage. Neither call needs one.
  • Each call is charged to your organization at what it cost (credits, or your organization's own bill with a dedicated worker) and shows in usage as web_searches and web_fetches. A call that fails is not charged. An account with no credits and no paid plan gets billing_required (402) before anything runs. A key made before these scopes existed does not have them: issue a new key.
curl -X POST https://machinus.xyz/api/fast/v1/search \
  -H "Authorization: Bearer mf_live_..." -H "Content-Type: application/json" \
  -d '{"query":"seed investors for devtools startups","limit":5}'
# -> {"id":"search_...","object":"search","sources":[{"title":"...","url":"...","highlights":["..."]}],...}

Capability · off by default

Saved logins and auto sign-in

When a site asks an end user to sign in, the agent can sign in for them with a login you saved. Turn it on with PATCH /organization {"auto_sign_in": true}, then save a login for the end user with PUT /accounts/{id}/credentials (scope credentials:manage).

curl -X PUT https://machinus.xyz/api/fast/v1/accounts/user_123/credentials \
  -H "Authorization: Bearer mf_live_..." -H "Content-Type: application/json" \
  -d '{"site":"https://app.example.com","username":"[email protected]","password":"...","totp_secret":"JBSWY3DPEHPK3PXP"}'
# -> {"id":"cred_k17abc","site":"https://app.example.com","has_one_time_code":true,...}   (never the password)
  • Kept in a vault, never readable. The username, password and one-time-code seed are stored in Machinus's encrypted credential vault. The API can list which sites have a login (GET) and remove one (DELETE .../credentials/{id}), but no call returns a login.
  • Bound to the site. A login is for one origin (scheme, host and port; https only, public). The agent's worker types it only when the browser is on exactly that origin, again checks after every step, and stops if a step goes to another site (a single sign-on service, a look-alike): nothing is typed there.
  • The model never sees it. The controller, not the model, signs in: when a sign-in page blocks a run, it asks for this end user's login for this exact site, and the worker types it. Results hold an outcome, never a value.
  • One-time codes. With a seed on file, the code the site asks for is generated and typed. A code sent by SMS or email, a login the site refuses, or a site with no saved login falls back to a takeover link. last_result on the saved login says what happened (for example rejected, so you can ask the user to update it).
  • With the browser profile. An end user's profile already keeps its sessions between runs, so a run usually finds itself signed in and never uses the login. It is used when a session has ended or the profile was reset (POST .../browser-profile/reset clears sessions and keeps the saved logins). Deleting the end user deletes its saved logins from the vault; each organization's end users are kept apart.
  • You see: a signed_in entry in the run's actions (the site, never the login). Up to 50 sites per end user.

Capability · off by default

Connected apps

Your end users can connect the apps where their facts already live (Gmail, Slack, Notion, Google Drive, and more), and the agent can look there for a field that Memory had no value for. Turn it on with PATCH /organization {"connected_apps": {"enabled": true, "toolkits": ["gmail", "slack"]}}. toolkits is optional: it narrows which apps your end users may connect (absent: any).

Connecting an app

curl -X POST https://machinus.xyz/api/fast/v1/accounts/user_123/connections \
  -H "Authorization: Bearer mf_live_..." -H "Content-Type: application/json" \
  -d '{"toolkit":"gmail","redirect_url":"https://app.example.com/connected"}'
# -> {"id":"conn_k17abc","toolkit":"gmail","status":"pending","connect_url":"https://…","expires_at":"..."}
  • Show connect_url to your end user. They sign in to the app themselves and land on a Machinus page, which sends them on to your redirect_url (an https address) when it is done. The link is good for 15 minutes.
  • You hear connection.connected (or connection.failed if the link ran out) as an event and a webhook. GET /accounts/{id}/connections lists what is connected and what is waiting; DELETE on one disconnects it.
  • Scope connections:manage. It can start, list and remove connections. It can never read what an app holds. An end user has up to 10 apps. A key made before this existed does not have the scope: issue a new key.

What a run does with them

  • The agent decides. For an end user who connected apps, the agent is told which, and looks in Memory first. Only for a field Memory had no value for, and only when it judges an app plausibly holds the answer, it searches that app for a read-only tool and reads with it. If neither Memory nor an app has the value, it asks the person, as before.
  • Read-only. It can fetch, list and search. It cannot send, create, change or delete anything in an app: a tool that is not a plain read is refused before the app is asked. Up to 20 reads per run. The agent is shown each tool's complete arguments (types, allowed values, nested fields), and if an app refuses a read because of its arguments it is shown what was wrong and corrects it; a refused read does not use up a read.
  • Cited. A value taken from an app has source.type connected_app and a ref like connected:gmail:18f3a, with the quote. The run's actions list a connected_read naming the app and the tool, never what came back.
  • Per run. A run for an end user uses them whenever the organization has them on. Send "connected_apps": "off" on POST /runs to turn them off for one run, or {"toolkits": ["gmail"]} to narrow it. A run with no account has no apps to read.

What to know

  • Isolation. Each end user's connections are their own. A run reads only its own end user's apps, within what the organization and the run allow, and only while it is acting.
  • Where the data goes. Machinus does not store the contents of a connected app. What the agent reads passes through the model that runs the agent, as Memory results do, so use it only with end users who agreed (require_consent still applies).
  • Deleting. Deleting an end user or an organization revokes their connected apps as a step of the deletion, and retries until it has.
  • Consent screens belong to the connected app's own sign-in. Using your own OAuth apps is not built.
  • Reads only. Sending an email or a message after approval is not built. Connected apps also do not write into Memory: a fact an app gave is used for that run.

For your end user

End-user page

POST /accounts/{id}/portal-link makes a page you can send to the end user. It shows what an agent does for them (and, for a ready run, exactly what it filled in), their consent, and what is kept about them. What they may do is the abilitiesyou allow: consent (give or withdraw), memory (remove what is kept) and approve (approve a prepared run, with the digest of what they were shown). Shown once; valid 7 days by default, up to 30 days. DELETE revokes every page made for them.

Staying informed

Events and webhooks

Every status change of a run, and account changes, is an event, kept 90 days (GET /events, filter by type, account or run). Webhooks send the same events to an https address.

EventWhen
run.createdA run was accepted.
run.runningThe agent is working (also after an answer or an approval).
run.needs_inputFacts are missing: the questions are in the event.
run.needs_takeoverA person has to act in the browser.
run.readyEverything is filled; nothing was submitted. Carries the preview_digest.
run.submittedThe approved form was submitted.
run.completedA task that is not a form finished.
run.failedThe run failed.
run.cancelledThe run was cancelled.
run.expiredA prepared page was discarded because nobody approved it in time.
account.createdAn end user was made.
account.consent_grantedYou recorded that an end user agreed.
account.consent_revokedYou recorded that an end user withdrew (its unsubmitted runs are cancelled).
account.deletedAn end user's deletion finished: everything held for them is erased.
connection.connectedAn end user finished connecting an app. Carries the connection.
connection.failedA connection link ran out before the end user finished.
connection.removedA connection was removed: by you, or because the end user disconnected the app (the reason says which).
  • POST /webhooks returns a signing secret once. Up to 20 per organization; subscribe to chosen types or all.
  • Each request is a POST of the event as JSON with Machinus-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256, keyed with the secret, of <t>.<raw body>; and Machinus-Event-Id. Reject a timestamp that is too old to stop a replay.
  • Delivery is at least once: ignore an event id you have seen. Any 2xx is success; otherwise it retries after 1 min, 5 min, 30 min, 2 h, 12 h, then gives up. Redirects are not followed. GET /webhooks/{id}/deliveries shows how each went.
// Verify (Node)
const [t, v1] = header.split(",").map((part) => part.split("=")[1]);
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) throw new Error("bad signature");

Metering

Usage and billing

  • GET /usage reads what your runs used and were charged, from the ledger the account is charged from: browser minutes, model tokens, Memory searches, web searches and CAPTCHAs solved, by run and by end user. Over 500 runs it reports the newest 500 and sets truncated.
  • GET /billing says how you are billed. Automatic dedicated workers use the owner's Fast credits. Existing organizations on a separate infrastructure subscription retain their organization bill.
  • Search and Fetch calls are included, as web_searches and web_fetches, under the end user they named if any.
  • Navigation tokens (the fast model that finds the form) show as navigation_tokens, and solved CAPTCHAs as captcha_solves. Usage is attributed to the endpoint and account that caused it.

Settings and operators

Organizations and operators

An organization is the customer a key acts for: its end users, events, webhooks, billing identity and dedicated worker. Its settings (PATCH /organization):

SettingWhat it does
nameThe organization's name (1 to 120 characters).
auto_sign_inLet its runs sign in with logins saved for the end user. Off by default. See Saved logins.
require_consentRefuse a run for an end user whose consent is not granted.
captcha_solvingLet its runs have a CAPTCHA solved. Off by default. See CAPTCHA solving.
proxySend its runs' browsers through a residential proxy: { enabled, country }. Off by default. See Proxy and zones.
stealthoff or standard: give each end user's browser a stable desktop of its own. Off by default. See Stealth.
connected_appsLet its end users connect apps (Gmail, Slack...) that their runs may read: { enabled, toolkits }. Off by default. See Connected apps.

An operator sells to customers of its own. With an organizations:manage key it makes an organization for each customer (POST /organizations, up to 200), issues each its keys, reads each one's usage and billing, and can delete them. Each is separate: its own accounts, Memory, events, webhooks, billing identity and automatically provisioned worker. An operator reaches only the organizations it owns, and its scope never goes into a customer's key.

Isolation

Dedicated worker

Creating an organization automatically starts its isolated managed runtime and persistent browser storage in the background, funded by the owner's active Fast subscription. No separate worker purchase or setup is required. If the organization was created before payment, provisioning starts when the subscription activates. A run requested before the worker is ready receives worker_unavailable; retry the same idempotency key once it is ready. Runs stay on that organization's worker, with a workspace of its own, and never fall back to a shared browser. Older organizations without a worker are repaired on their first run. Usage is charged to the owner's credits; managed-runtime cost is reconciled once per closed billing period, including idle time.

Repeat work

Automations

An automation is a run saved to be made again: the page, the goal, who it is for and the facts it starts with (up to 500 per organization). POST /runs/{id}/automation saves a run as one; POST /automations/{id}/runs makes a new run from it, prepared and never submitted like any other. Each automation records whether what its latest run found changed since the one before, by a fingerprint of the filled values. It can be paused and resumed.

What runs your forms

The browser

Every run drives real Google Chrome (current stable, on Linux), headed on a private virtual screen, not a headless one. Each end user has one persistent browser profile on the worker's volume, so cookies, local storage and sign-ins carry from run to run. The live view and the takeover link show that same screen. One Chrome starts in about a second and a half; reading a form and filling it take milliseconds on the page itself.

What a site can read from it

This is what a page can see from a run with stealth off, which is the default, so you can judge whether your target sites will accept it. The Stealth section says what the standard level changes.

What a site readsWhat it sees
Browser and windowGoogle Chrome on Linux, a 1440×900 window and screen. It does not say HeadlessChrome, and navigator.webdriver is false.
Network addressThe platform's datacenter address, or a residential one when the organization turns the proxy on.
Clock and languageUTC and English (US). With the proxy on and a country the platform knows, that country's timezone and language.
WebRTCReveals the server's own address, unless the proxy is on: then WebRTC is limited to the proxy and gives out nothing.
GraphicsNo graphics card, and WebGL is unavailable.
CPU cores and memoryThe server's: a core count and memory size larger than any laptop's.
Screen1440×900 for every end user, with no taskbar or panel: the available area is the whole screen.

A person who takes over sees Chrome's yellow bar about an unsupported command-line flag (the browser runs without its sandbox, as containers require). It is harmless and does not appear to the page.

Capability · off by default

Stealth

Turn it on with PATCH /organization {"stealth": "standard"}. Each end user then has one machine of their own that their browser reports on every run: a screen, a CPU core count, a memory size, a graphics card and a desktop panel, drawn once for the end user and kept with their browser profile. The operating system and the browser stay what they are (Linux, Google Chrome): pretending to be Windows from a Linux server would be contradicted by fonts and rendering, so nothing is pretended that a page could check.

What a page readsOffStandard
Screen, available area, window1440×900, all the sameThe end user's screen, less a 27 to 48 px panel; the window fills what is left
CPU cores, memoryThe server's (48 cores, 32 GB)The end user's (4 to 16 cores, 4 or 8 GB), in pages, frames and workers
WebGLNo contextPresent, naming a desktop graphics card
Visitor identifier (FingerprintJS)The same for every end userDifferent for each end user, the same on every run
Bot-detector pages (sannysoft, CreepJS)WebGL rows fail; 44% like headlessNothing flagged on sannysoft; 38% like headless
  • One machine per end user. A run for the same end user is always the same machine, and another end user is another. Resetting an end user's browser profile keeps their machine; deleting the end user removes it.
  • Changing it. A browser started for another setting is restarted at the next run, never while another run holds a prepared page in it. Cookies and sign-ins are kept.
  • Fails closed. If the machine's files are missing, the run fails instead of driving an unmasked browser.
  • Built from fingerprint-suite. The machines are drawn from its generator data (Apache-2.0) and checked against what our browser can honestly be; its helper code keeps the overridden properties looking native.

Stealth and the proxy are two settings

stealth makes the browser look like a believable desktop. proxy changes where its traffic comes from. Neither turns the other on. With stealth alone, a site still sees the platform's datacenter address; with the proxy alone, it sees a home-type address on a browser that still reads as a server. For the best result turn on both. Stealth is one switch with nothing to configure inside it: the machine of each end user is chosen by the platform, and there is no level above standard yet.

What it does not do

  • A page that a popup or target=_blank opens with its address already given can read the real values from its very first lines; it is covered a moment later.
  • WebGL's other values, and what a canvas or audio test draws, are still this server's, the same for every end user.
  • Between runs, with no one attached, a new tab is plain. A person taking over is covered while the preview of the paused page runs.
  • It does not hide that a browser is automated from a detector that looks for what an automated browser does, and it does not change the network address: for that, turn on the proxy.

Capability · off by default

CAPTCHA solving

Turn it on with PATCH /organization {"captcha_solving": true}. Its runs then have a managed solver tool.

  • Solves: reCAPTCHA v2 (checkbox and invisible, enterprise too), reCAPTCHA v3, and Cloudflare Turnstile, found by their markup in the page. hCaptcha and anything else is reported as unsupported and falls back to a takeover link, as do a solver failure, an empty balance and a timeout.
  • When: an answer expires within minutes (about two for reCAPTCHA), so the agent solves only when a CAPTCHA blocks the page and, on approval, just before it presses the final control. Never while a form is only being prepared.
  • You see: a captcha_solved entry in the run's actions (the kind, never the answer) and a captcha_solves count in its usage.
  • Limit: at most 10 solves per run, since each is billed by the solver.
  • Not covered: a CAPTCHA inside an embedded form's iframe, and image or slider challenges.

Capability · off by default

Private proxy

Turn it on with PATCH /organization {"proxy": {"enabled": true, "country": "us"}}. Its runs' browsers then go out through Machinus's managed private network. country is optional: a two-letter country code, or eu for a random country of the European Union; absent, the network chooses. The setting is replaced as a whole each time you send it.

  • Same address per end user. Each end user's browser profile gets its own session, so they keep one exit address for as long as the network can. The network receives a one-way account reference, never your ids.
  • The browser agrees with the address. For a country the platform knows (about fifty; not eu or an unlisted one), the browser reports that country's timezone and language, and WebRTC can no longer reveal the server's own address around the proxy. One timezone per country: the one with most of its people.
  • Persistent. A browser keeps its proxy for its life. A run that wants another setting restarts the browser first (cookies and sign-ins are kept), but never while another run holds a prepared page in it: that waits for the next start.
  • Fails closed. If you asked for the proxy and it is not available, the run fails instead of going out unproxied.
  • One setting per organization. Choose a country for the whole organization, not a country per end user or a state or city.
  • Takeover works through it. A person taking over reaches pages through the same proxy.

Privacy

Deletion

Deleting an end user, or a whole organization, answers at once (202 and a deletion to follow with GET /deletions/{id}) and erases in the background in steps that can each be repeated, so a worker that cannot be reached or a managed dependency that is down is retried, never skipped. A deletion is in_progress, completed or, if it could not be finished after repeated attempts, failed and visible.

  • An end user (DELETE /accounts/{id}): their Memory (notes and earlier versions), their browser profile on the runtime or dedicated worker that holds it, their runs and everything those left, their records, automations and portal links. The account is gone from the API at once and its id cannot be made again until the deletion completes. Refused while a run for it is in progress. An account.deleted event is sent when it is done.
  • An organization (DELETE /organization, or an operator's DELETE /organizations/{id}): every end user as above, its runs, events, webhooks and keys, and its billing identity. confirm (its name or id) is required. A dedicated worker is removed first, and an active subscription must be cancelled first. Its keys stop working at once. Usage not yet reported to billing is kept until it has been.
  • What stays: cost records of usage already billed (counts and amounts, labelled with your own account id), which are financial records, and the deletion itself as a receipt with the end user's id kept only as a hash.

Numbers

Limits

WhatLimit
Runs working at once, queued, or waiting prepared for approvalNo limit
End users per organization5,000
Facts per end user / per save call500 / 50
Document size15 MB
Records per end user500
Saved logins (sites) per end user50
Automations per organization500
Webhooks per organization20
Organizations per operator200
CAPTCHA solves per run10
Result of a run (`summary`)12,000 characters
Page text the agent reads for a task20,000 characters; a longer page is read up to there and the run says so
Items the agent reads from one list on a page40 per list (each cut at 480 characters); the page's own count is kept, and the rest of a long list is only in the page text
Events and delivery records kept90 days
Memory search results per call24
List page size100

Reference

Endpoint reference

Every path is under https://machinus.xyz/api/fast/v1. The machine-readable contract is the OpenAPI file docs/machinus-fast-openapi.yaml in the repository.

Browser Agent

Prepare a form, answer what is missing, hand a person the browser, approve, cancel.

POST /runs
Create a run
runs:write
GET /runs
List runs, newest first
runs:read
POST /runs/{runId}/automation
Save this run as an automation
runs:write
GET /runs/{runId}
Read a run
runs:read
GET /runs/{runId}/output
What was answered
runs:read
GET /runs/{runId}/actions
What the agent and the person did, in order
runs:read
POST /runs/{runId}/answers
Answer the open questions
runs:write
POST /runs/{runId}/resume
Continue after a takeover
runs:write
POST /runs/{runId}/takeover-link
Make a link for the person who has to act in the browser
runs:write
POST /runs/{runId}/approve
Submit what was prepared
runs:approve
POST /runs/{runId}/cancel
Cancel a run that has not been submitted
runs:write

Users

Your own users, each with a Memory and a browser profile of their own; consent; deletion.

GET /accounts
List your end-user accounts
accounts:read
PUT /accounts/{accountId}
Make an end-user account, or update its name and metadata
accounts:write
GET /accounts/{accountId}
Read an end-user account
accounts:read
DELETE /accounts/{accountId}
Delete an end user and everything held for them
accounts:write
GET /deletions/{deletionId}
Where a deletion stands
accounts:read
PUT /accounts/{accountId}/consent
Record whether this end user agreed that an agent acts for them
accounts:write
GET /accounts/{accountId}/consent
Read this end user's consent
accounts:read

Memory, documents and browser profile

What an end user's agent knows and keeps, and a page the end user can open.

GET /accounts/{accountId}/memory/search
Search an end user's Memory
accounts:read
POST /accounts/{accountId}/documents
Keep a document for an end user
accounts:write
GET /accounts/{accountId}/documents
An end user's documents, newest first
accounts:read
GET /accounts/{accountId}/documents/{documentId}
One document
accounts:read
DELETE /accounts/{accountId}/documents/{documentId}
Delete a document
accounts:write
PUT /accounts/{accountId}/credentials
Save the login an end user's agent signs in with on a site
credentials:manage
GET /accounts/{accountId}/credentials
The sites an end user has a saved login for
credentials:manage
DELETE /accounts/{accountId}/credentials/{credentialId}
Remove a saved login
credentials:manage
GET /connected-apps/toolkits
Whether connected apps are on, and which apps end users may connect
connections:manage
POST /accounts/{accountId}/connections
Start connecting an app (Gmail, Slack, Notion...) for an end user
connections:manage
GET /accounts/{accountId}/connections
The apps an end user has connected, and those waiting for them to finish
connections:manage
DELETE /accounts/{accountId}/connections/{connectionId}
Disconnect an app
connections:manage
POST /accounts/{accountId}/browser-profile/reset
Reset an end user's browser profile
accounts:write
PUT /accounts/{accountId}/memory
Put facts into an end user's Memory
accounts:write
GET /accounts/{accountId}/memory
List an end user's facts
accounts:read
DELETE /accounts/{accountId}/memory
Remove one fact
accounts:write
GET /accounts/{accountId}/resources
The records kept about an end user
accounts:read
PUT /accounts/{accountId}/resources/{kind}/{resourceId}
Keep a record about an end user
accounts:write
GET /accounts/{accountId}/resources/{kind}/{resourceId}
Read one
accounts:read
DELETE /accounts/{accountId}/resources/{kind}/{resourceId}
Remove one
accounts:write
POST /accounts/{accountId}/portal-link
Make a page for this end user
accounts:write
DELETE /accounts/{accountId}/portal-link
Revoke every page made for this end user
accounts:write

Automations

A run saved to be made again, with a note of whether what it found changed.

POST /automations
Save a run to be made again
runs:write
GET /automations
Your automations, newest first
runs:read
GET /automations/{automationId}
Read an automation
runs:read
PATCH /automations/{automationId}
Pause or resume it, or change what it does
runs:write
DELETE /automations/{automationId}
Remove it
runs:write
POST /automations/{automationId}/runs
Make a run from the saved definition
runs:write
GET /automations/{automationId}/runs
The runs this automation made
runs:read

Search and Fetch

Search the public web and read pages by address, without a browser run. Each call is charged on its own.

POST /search
Search the public web
search:read
POST /fetch
Read public pages by address
fetch:read

Events and webhooks

What happened, in order, and where to be told.

GET /events
What happened, newest first
events:read
POST /webhooks
Send events to an https address
webhooks:manage
GET /webhooks
List webhooks
webhooks:manage
PATCH /webhooks/{webhookId}
Disable, enable or change what it receives
webhooks:manage
DELETE /webhooks/{webhookId}
Remove it
webhooks:manage
GET /webhooks/{webhookId}/deliveries
What was sent to it and how it went
webhooks:manage

Usage and billing

What runs used, and what an organization is being billed.

GET /usage
What your runs used and were charged
usage:read
GET /billing
What your organization is being billed
usage:read

Organizations and operators

Your organization's settings and the organizations you make for your own customers.

GET /organization
Your organization
organization:manage
PATCH /organization
Rename it, or set its consent, CAPTCHA and proxy settings
organization:manage
DELETE /organization
Delete the organization, with everything it holds
organization:manage
POST /organizations
Make an organization for one of your customers
organizations:manage
GET /organizations
Every organization you own, your own first
organizations:manage
GET /organizations/{orgId}
One of your organizations
organizations:manage
DELETE /organizations/{orgId}
Delete one of your organizations, with everything it holds
organizations:manage
GET /organizations/{orgId}/usage
What one of your organizations used, in the shape of its own GET /usage
organizations:manage
GET /organizations/{orgId}/billing
What one of your organizations is being billed, in the shape of its own GET /billing
organizations:manage
POST /organizations/{orgId}/keys
Issue a key for one of your organizations
organizations:manage
GET /organizations/{orgId}/keys
An organization's keys
organizations:manage
DELETE /organizations/{orgId}/keys/{keyId}
Revoke one of an organization's keys
organizations:manage

Honest status

Not built yet

  • Approved writes to connected apps. Sending an email or a message, or adding a row, in an app an end user connected, after you approve the run. Connected apps are read-only today.
  • Your own OAuth apps for connected apps, so the sign-in screen carries your name.
  • A stronger stealth level. Today there is one: standard. Making canvas and audio output differ per end user, and measuring against more detectors, are not built.
  • A timezone for the city rather than the country (a country with several timezones always reports its main one). Geolocation is left unanswered on purpose: a site that asks for the location gets a plain denial.
  • Hiding Chrome's unsupported-flag bar from the takeover view.
  • Saving a login automatically when a person signs in during a takeover (with their agreement). Today a login is saved through the API.
  • Splitting the proxy zone's bandwidth and cost by organization. It is read for the whole zone today.
  • A per-end-user proxy country, and state, city, ZIP and ASN targeting.
  • A server-sent events stream for a single run, and a quick form-inspection call that fills nothing.
  • Search and Fetch are charged at what they cost, with no price of their own and no rate limit yet.
  • on_missing other than return_questions (a run that fills what it knows and lists the gaps), a run-level ttl_seconds, and a memory.scope option.
Machinus Fast API