Security & control
Automation you control, by construction.
Every run is authenticated to your tenant and aimed at the URL you passed. Isolation is a Postgres policy, keys are stored only as hashes, credentials never leave the vault, and every call writes an audit row.
Controls
What's enforced, and how it fails safe.
Each layer is independent. When something is wrong — an invalid key, an unreachable vault, a cross-tenant read — the run stops or returns nothing rather than guessing.
You direct every run
Nothing runs that you did not ask for. Each call is authenticated with your API key and acts on exactly the target URL you provide — the URL you pass is the instruction. There is no autonomous crawling and no action you did not initiate.
You hold the authorization
You target only the sites you own or are authorized to automate — that responsibility stays with you, where it belongs. Every run stamps the target it acted on into the record, so there is always a clear trail of what was done, and where.
Default-deny RLS isolation
Tenancy is enforced in the database, not just the app. Postgres Row-Level-Security scopes every row to its owner with default-deny policies, so one tenant can never read or drive another tenant's keys, skills, secrets, or run history.
Hashed, per-tenant API keys
The raw API key is shown exactly once, at creation. We store only a hash and metadata — a leaked database never yields a usable key, and revocation fails closed.
Audit log on every call
Every run records its goal, the path taken, the success condition, and the target it acted on. The result is an auditable trail of exactly what each agent did, on which target, and under which key.
Shared skill library — sanitized
When a task succeeds, its sanitized navigation skeleton joins a cross-tenant library so everyone automating the same host compiles faster. Only the structure crosses tenants — the host and which UI elements were used.
Credential vault: secrets the model never sees.
Store a credential once; reference it by name. Twin fills the value at the keystroke, inside the browser, on your own runs — it never reaches the model, the logs, the audit record, or the shared corpus.
# Store a credential once — the value goes to the encrypted vault.
curl -X POST https://twin-browser.com/api/v1/secrets \
-H "Authorization: Bearer ab_live_…" -H "Content-Type: application/json" \
-d '{ "name": "acme_password", "value": "•••••••••" }'
# Reference it by name in a run — Twin fills it at the keystroke,
# inside the browser, on your own run only.
curl -X POST https://twin-browser.com/api/v1/run \
-H "Authorization: Bearer ab_live_…" -H "Content-Type: application/json" \
-d '{ "url": "https://app.acme.com/login",
"prompt": "sign in", "secrets": ["acme_password"] }'
# The value never reaches the model, the logs, the audit record,
# or the shared corpus. Only the reference name is ever stored.Authorization
The target URL is the authorization.
Twin Browser runs exactly the URL you pass and audits the call. Authorization stays with you, the caller — you target only the sites you own or are authorized to automate. This is not a CAPTCHA-bypass or anti-bot-evasion service.
Authorized use, plainly
First-party apps, operator-approved automation, internal RPA, accessibility, and authorized testing. Auth, billing, and audit logging run on every call. See the acceptable-use policy for the full boundary.
FAQ
Security, answered.
The five questions a security reviewer asks first, answered with the mechanism rather than a posture statement.
How is one tenant isolated from another?
Do my credentials ever reach the model?
What is the authorization model?
What gets logged on each call?
What crosses tenants in the shared skill library?
Built for authenticated execution.
Every run is authenticated to a tenant and directed by you. Every key is scoped. Every action is logged.