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| Scope | What it allows |
|---|---|
runs:write | Create, answer, resume, cancel and automate runs; make takeover links. |
runs:read | Read runs, their output and their actions. |
runs:approve | Approve a prepared run. This is the only scope that can submit anything. |
accounts:write | Make, change and delete end users; their Memory, documents, records, consent, portal links and browser profile. |
accounts:read | Read end users and what is kept for them; search their Memory. |
credentials:manage | Save, list and remove the logins an end user's agent signs in with. It can never read one back. |
connections:manage | Start, 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:read | Search the public web (POST /search). Each call is charged on its own. |
fetch:read | Read public pages by address (POST /fetch). Each call is charged on its own. |
events:read | Read the event log. |
usage:read | Read usage and billing. |
webhooks:manage | Make, change and remove webhooks and read their deliveries. |
organization:manage | Read and change your organization's settings, and delete it. |
organizations:manage | An 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-KeyonPOST /runsandPOST /automations/{id}/runs: a repeated key returns the original run; the same key with a different request isidempotency_conflict.- Lists take
limit(up to 100); those that can continue returnnext_cursor. Fields are only ever added inside/v1. - Every error has one shape:
{"error":{"code","message","details"}}.
| Code | Status | Meaning |
|---|---|---|
invalid_request | 400 | The request is malformed or breaks a rule (a field, a size, an id). |
unauthorized | 401 | No key, or an unknown, revoked or expired one. |
forbidden | 403 | The key lacks the scope this call needs. |
run_not_found | 404 | No such run for this key's organization. |
account_not_found | 404 | No such end user. Make it first, or use a key with accounts:write. |
webhook_not_found | 404 | No such webhook. |
organization_not_found | 404 | No such organization among those you own. |
resource_not_found | 404 | No such record, document or deletion. |
fact_not_found | 404 | No such fact for this end user. |
automation_not_found | 404 | No such automation. |
billing_required | 402 | A dedicated worker is billed to the organization, and its subscription is not active. |
worker_unavailable | 503 | The dedicated worker, browser runtime, or a managed service could not do it right now. Try again. |
key_not_found | 404 | No such key. |
consent_required | 409 | The organization requires consent and this end user has not granted it. |
webhooks_unavailable | 503 | Webhooks are not available on this deployment. |
invalid_state | 409 | The run, account or organization is in a state that does not allow this (for example approving a run that is not ready). |
idempotency_conflict | 409 | This Idempotency-Key was already used with a different request. |
link_invalid | 404 | A takeover or portal link that is unknown. |
link_expired | 410 | A takeover or portal link that has expired or ended. |
internal_error | 500 | Something 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
}| Status | Meaning |
|---|---|
queued | Accepted, waiting for a worker. There is no limit on how many runs queue. |
running | The agent is working. |
needs_input | Facts are missing. `questions` lists them; answer with POST /runs/{id}/answers. |
needs_takeover | A person must act (sign in, a code, a CAPTCHA that could not be solved). Make them a link with POST /runs/{id}/takeover-link. |
ready | Everything is filled and checked; nothing was submitted. `preview_digest` is what approve must echo. |
submitted | You approved and the form was submitted. |
completed | A task that is not a form finished. |
failed | The run failed. `error` says why. |
cancelled | Cancelled by you, or because consent was withdrawn. |
expired | A prepared page nobody approved in time was discarded. |
GET /runs/{id}shows the status,fields(each with itssource: memory, context, answer, document or page),questionsinneeds_input,readinessandpreview_digestinready, andusage.GET /runs/{id}/outputis what was answered, by your own keys, plus what is still unanswered.GET /runs/{id}/actionsis 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.modesays what a run does at the end.prepare(the default) stops in front of the final control and waits forapprove.completepresses it by itself, once, after the run's safeguards pass, and needs a key withruns:approve(403otherwise). Acompleterun that fails a safeguard stopsreadylike apreparerun, andcompletion.heldsays why;completion.auto_approved_atshows when it approved itself, and/actionsshows anauto_approvedentry before thesubmit. The run always shows itsmode.approveis the call that submits, apart from acompleterun, which makes it itself. It works only inreadyand only with the run's currentpreview_digest; repeating it returns the first result.- A prepared page that nobody approves is discarded: the run becomes
expired(expires_atsays 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}/resumecontinues 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}/consentrecords that the end user agreed (it is your word: you are the one who asked). Withrequire_consenton the organization, a run for an end user who has not granted it is refused. Revoking cancels their unsubmitted runs. - Records.
/accounts/{id}/resourceskeeps 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/resetclears 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}/memorysaves 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.GETlists them;DELETEremoves 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:fileand optionalkindof 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 /searchsearches the public web and returns sources with highlights. Sendquery; optionallylimit(up to 20),effort(minimaltoxhigh; a higher one costs more),source_categoriesand anoutput_schemato get the answer back in your own shape. Scopesearch:read. A call with noeffortruns at the medium level; the effort is what sets what a call costs.POST /fetchreads up to 8 public pages you already know the address of, as highlights or full text, with a stablecontent_hashso your own scheduler can tell when a page changed. Scopefetch:read.- Name an end user in
accountand the call shows under them inGET /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_searchesandweb_fetches. A call that fails is not charged. An account with no credits and no paid plan getsbilling_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_resulton the saved login says what happened (for examplerejected, 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/resetclears 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_inentry 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_urlto your end user. They sign in to the app themselves and land on a Machinus page, which sends them on to yourredirect_url(an https address) when it is done. The link is good for 15 minutes. - You hear
connection.connected(orconnection.failedif the link ran out) as an event and a webhook.GET /accounts/{id}/connectionslists what is connected and what is waiting;DELETEon 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.typeconnected_appand areflikeconnected:gmail:18f3a, with the quote. The run'sactionslist aconnected_readnaming 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"onPOST /runsto turn them off for one run, or{"toolkits": ["gmail"]}to narrow it. A run with noaccounthas 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_consentstill 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.
| Event | When |
|---|---|
run.created | A run was accepted. |
run.running | The agent is working (also after an answer or an approval). |
run.needs_input | Facts are missing: the questions are in the event. |
run.needs_takeover | A person has to act in the browser. |
run.ready | Everything is filled; nothing was submitted. Carries the preview_digest. |
run.submitted | The approved form was submitted. |
run.completed | A task that is not a form finished. |
run.failed | The run failed. |
run.cancelled | The run was cancelled. |
run.expired | A prepared page was discarded because nobody approved it in time. |
account.created | An end user was made. |
account.consent_granted | You recorded that an end user agreed. |
account.consent_revoked | You recorded that an end user withdrew (its unsubmitted runs are cancelled). |
account.deleted | An end user's deletion finished: everything held for them is erased. |
connection.connected | An end user finished connecting an app. Carries the connection. |
connection.failed | A connection link ran out before the end user finished. |
connection.removed | A connection was removed: by you, or because the end user disconnected the app (the reason says which). |
POST /webhooksreturns a signingsecretonce. 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>, wherev1is the HMAC-SHA256, keyed with the secret, of<t>.<raw body>; andMachinus-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}/deliveriesshows 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 /usagereads 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 setstruncated.GET /billingsays 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_searchesandweb_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 ascaptcha_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):
| Setting | What it does |
|---|---|
name | The organization's name (1 to 120 characters). |
auto_sign_in | Let its runs sign in with logins saved for the end user. Off by default. See Saved logins. |
require_consent | Refuse a run for an end user whose consent is not granted. |
captcha_solving | Let its runs have a CAPTCHA solved. Off by default. See CAPTCHA solving. |
proxy | Send its runs' browsers through a residential proxy: { enabled, country }. Off by default. See Proxy and zones. |
stealth | off or standard: give each end user's browser a stable desktop of its own. Off by default. See Stealth. |
connected_apps | Let 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 reads | What it sees |
|---|---|
| Browser and window | Google Chrome on Linux, a 1440×900 window and screen. It does not say HeadlessChrome, and navigator.webdriver is false. |
| Network address | The platform's datacenter address, or a residential one when the organization turns the proxy on. |
| Clock and language | UTC and English (US). With the proxy on and a country the platform knows, that country's timezone and language. |
| WebRTC | Reveals the server's own address, unless the proxy is on: then WebRTC is limited to the proxy and gives out nothing. |
| Graphics | No graphics card, and WebGL is unavailable. |
| CPU cores and memory | The server's: a core count and memory size larger than any laptop's. |
| Screen | 1440×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 reads | Off | Standard |
|---|---|---|
| Screen, available area, window | 1440×900, all the same | The end user's screen, less a 27 to 48 px panel; the window fills what is left |
| CPU cores, memory | The server's (48 cores, 32 GB) | The end user's (4 to 16 cores, 4 or 8 GB), in pages, frames and workers |
| WebGL | No context | Present, naming a desktop graphics card |
| Visitor identifier (FingerprintJS) | The same for every end user | Different for each end user, the same on every run |
| Bot-detector pages (sannysoft, CreepJS) | WebGL rows fail; 44% like headless | Nothing 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=_blankopens 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_solvedentry in the run's actions (the kind, never the answer) and acaptcha_solvescount 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
euor 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. Anaccount.deletedevent is sent when it is done. - An organization (
DELETE /organization, or an operator'sDELETE /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
| What | Limit |
|---|---|
| Runs working at once, queued, or waiting prepared for approval | No limit |
| End users per organization | 5,000 |
| Facts per end user / per save call | 500 / 50 |
| Document size | 15 MB |
| Records per end user | 500 |
| Saved logins (sites) per end user | 50 |
| Automations per organization | 500 |
| Webhooks per organization | 20 |
| Organizations per operator | 200 |
| CAPTCHA solves per run | 10 |
| Result of a run (`summary`) | 12,000 characters |
| Page text the agent reads for a task | 20,000 characters; a longer page is read up to there and the run says so |
| Items the agent reads from one list on a page | 40 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 kept | 90 days |
| Memory search results per call | 24 |
| List page size | 100 |
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.
Users
Your own users, each with a Memory and a browser profile of their own; consent; deletion.
Memory, documents and browser profile
What an end user's agent knows and keeps, and a page the end user can open.
Automations
A run saved to be made again, with a note of whether what it found changed.
Search and Fetch
Search the public web and read pages by address, without a browser run. Each call is charged on its own.
Events and webhooks
What happened, in order, and where to be told.
Usage and billing
What runs used, and what an organization is being billed.
Organizations and operators
Your organization's settings and the organizations you make for your own customers.
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
eventsstream 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_missingother thanreturn_questions(a run that fills what it knows and lists the gaps), a run-levelttl_seconds, and amemory.scopeoption.