Docs menu

Settings

Reusable credentials

Ask a bot to connect an API using a key or password. It calls request_secret with a reference name and an HTTPS service origin. The protected card shows that origin before you save. Web, Tauri and mobile use the same backend; the value is cleared from the input when submission starts and is omitted from messages, events and model input.

For example, the bot can request:

{
  "label": "API key",
  "purpose": "api_key",
  "credential": {
    "name": "example_api",
    "origin": "https://api.example.test",
    "auth": { "type": "bearer" }
  }
}

The value is encrypted in Postgres vault_credentials using the deployment’s ENCRYPTION_KEY (AES-256-GCM with record-bound authentication). Only the run’s user can submit a reusable credential. It belongs to that user and survives later runs. By default it sits in the user’s account vault, which every Space uses; a Space where the user keeps its own credentials has a separate vault that starts empty, so they have to be saved there again, and no other Space can read it. It is deleted when its owner is deleted, and a Space’s own vault is also deleted with its Space. Names, destinations and authentication configuration are metadata; list_secrets returns those fields without values. No hosted credential service or prebuilt connector is required.

The bot uses secret_request to ask the backend to decrypt and inject the credential:

{
  "name": "example_api",
  "url": "https://api.example.test/v1/items",
  "method": "GET"
}

Authentication supports Bearer tokens, a named header ({"type":"header","name":"X-Api-Key"}), and Basic authentication ({"type":"basic","username":"api-user"}). Basic uses the protected value as the password. Requests can send JSON, form-encoded or plain text bodies. The saved origin must match exactly, including port. Redirects and unsafe network destinations are rejected through the shared remote-request policy. Requests follow the existing action approval rules, have a 30-second deadline and a 1 MB response limit, and return at most 20,000 characters of redacted response content without response headers.

Self-hosted deployments can set ORKBOT_SECRETS_ALLOW_PRIVATE_HTTP=1 to also allow plain http:// origins on private or loopback addresses (for example http://nas.local:8123 or http://localhost:3000). It is off by default. Credentials sent this way are unencrypted: anything on that network, or any local service on that port, can read them. Enable it only on a network you trust. Link-local and cloud metadata addresses stay blocked, and public origins still require HTTPS.

Calling request_secret again with the same name and configuration returns the saved reference. Add "replace": true to show another protected card and replace its value. forget_secret with {"name":"example_api"} removes the stored value and prevents future use; an already-started request may finish. Remove a credential before changing its origin or authentication configuration. Each user can save up to 100 credentials per bot, with values limited to 16,384 characters.

The model has no tool for reading these values, and the backend removes direct and common encoded echoes from API responses. The approved service still receives the credential: redaction cannot defend against a malicious service deliberately transforming it. Choose a service you trust and use appropriately scoped credentials.

This boundary supports authenticated HTTP requests. Injecting a site credential into an AI-controlled shell command, file or arbitrary environment variable would let those commands read it, so those paths are not exposed. A value you deliberately want in the bot’s own shell is a command variable, below. APIs needing request signing, OAuth refresh, multiple credentials or custom protocols should use a connector adapter. Existing request_secret calls with a connectionId retain their one-use connector-code flow.

Command variables

When a value belongs in the bot’s own shell rather than an HTTP header, save it as a command credential. The bot calls request_secret with "auth": {"type":"command"} and no origin:

{
  "label": "NetBird setup key",
  "purpose": "api_key",
  "credential": {
    "name": "netbird-setup-key",
    "auth": { "type": "command" }
  }
}

The name is exported to that bot’s shell commands as an environment variable: upper case with hyphens as underscores, so netbird-setup-key becomes $NETBIRD_SETUP_KEY. The environment is loaded fresh before every command, so a variable added, replaced or removed mid-run applies to the next command.

A command variable overrides a space variable exported under the same name. That is accepted and owner-controlled: you decide which environment a bot’s commands run in, and a per-bot value is the narrower choice, so it wins over the space-wide value it shadows. Remove the space variable, or rename the command variable, if you want the space value instead.

If a saved bot command variable cannot be loaded, that variable is unset for the command, even when a space variable has the same name. Other variables remain available; no credential value or load error is exposed.

A command variable is redacted from command output in its plain, base64 and URL-encoded forms, and it cannot be used with secret_request or a browser_act login fill. Names that change how a shell or a common runtime starts, trust TLS, or run git or ssh helpers are reserved, as are names beginning with LD_, DYLD_, ORKBOT_, GIT_CONFIG_ or NPM_CONFIG_. The reserved list is not exhaustive: it covers the well-known variables, and saving a reserved name is refused with the reason.

The bot’s shell reads this value. A command variable is a convenience for the bot, not a boundary against it: the shell command the bot runs, and the code that command starts, can print, encode or send the value. Redaction stops it appearing in a result the model reads; it does not stop a permitted bot, or anything the bot runs, from using it. There is no secrecy from the bot that was given the credential, so treat a command variable as shared with that bot and save only values you would let it use unattended, scoped to what it needs.

Command variables are managed in the same Vault as other reusable credentials. The request card a bot opens in a thread shows a command variable’s variable name in place of a site.

Saved website logins

When you want a bot to sign in to a website by itself, ask it to save the login. It calls request_secret with "auth": {"type": "login"}, the origin of the sign-in page, and the labels of the values the site signs in with:

{
  "label": "Example sign-in",
  "purpose": "password",
  "credential": {
    "name": "example_login",
    "origin": "https://login.example.test",
    "auth": { "type": "login" }
  },
  "fields": ["ID number", "Phone number"]
}

Without fields, the card asks for a Username, which starts visible, and a Password, which starts hidden, as logins always have. A login has 1 to 8 fields, each with its own label. The card asks for one value per field, and a field the bot names starts visible only if every word of its label is an identifier word (ID, phone, email, account, username) or a plain filler word (number, name, address, last, digits), in English or Hebrew, such as “ID number” or “Phone number”; any other word, such as a password, PIN or code, or a misspelling, keeps it hidden, as does every label that isn’t recognised. You can switch any field between hidden and visible before saving, and what you save is final: the bot names the fields but cannot send a visible flag, so the label is all it can influence, and the server only accepts an answer with exactly the fields the card asked for. All values are stored encrypted in the same way as other reusable credentials.

The card also has a Save to Vault switch, on by default, that only you can turn off. With it off, the login is kept encrypted for the run that asked for it and never goes into a vault. Only that run can fill it, on the card’s HTTPS site, and it is hidden from tool output like a saved login. It is deleted when the run ends, however it ends, so a later run has to ask again. The site’s cookies stay, so the bot may still be signed in.

To sign in, the bot fills fields found with browser_snapshot, naming each by its label:

{
  "actions": [
    { "kind": "fill_secret", "ref": "e3", "secret": "example_login", "field": "ID number" },
    { "kind": "fill_secret", "ref": "e4", "secret": "example_login", "field": "Phone number" },
    { "kind": "click", "ref": "e5" }
  ]
}

"username" and "password" also name a login’s first visible and first hidden field, so a bot can fill a login saved in settings without knowing its labels.

The backend decrypts the value and the page browser types it only if the page is still on the saved origin at that moment, so a login cannot be typed into another site or a page that redirected. The origin must be HTTPS: ORKBOT_SECRETS_ALLOW_PRIVATE_HTTP does not apply to website logins. A hidden field goes only into a password field, and a visible field only into a text, email or tel input. A visible field can appear on the page and in screenshots. A site’s “show password” control still works normally; revealing a hidden value can make it visible in screenshots. Values travel to the computer over stdin, not command-line arguments. Snapshots never report the value of a field that holds a saved login. In every run, not only the one that filled it, tool results such as page results, command output and file reads are scrubbed of each saved login’s hidden fields in plain, URL-encoded, form-encoded and base64 form; hidden values shorter than 6 characters are only scrubbed in the run that fills them. In the run that fills a login, page results are also scrubbed of its visible fields of 6 or more characters, which covers a site that echoes them in page text; shorter visible values are not scrubbed because redaction replaces every occurrence. Later runs leave visible fields readable, since one is often the user’s own email address. Only this bot’s own runs scrub its hidden fields: on a Team Computer, other bots’ runs don’t, so use a Private Computer for a bot that signs in to sites. A login cannot be used with secret_request. Fills need no approval: saving the login is the approval, and it is bound to one site. Use forget_secret to remove it.

Logins saved before fields existed read as a visible Username and a hidden Password, and fill exactly as before.

Payment cards

A bot asks for a card with request_secret and auth { "type": "card" }, with no origin. The card asks for the number, expiry, security code, name on card and, optionally, a saved contact whose address bills it. The whole card is encrypted like any other value. list_secrets shows the bot only its brand, last four digits, expiry, name and billing contact, never the number or security code.

The bot types a saved card with browser_act fill_card, passing card and site, the checkout page’s HTTPS origin:

{
  "actions": [
    { "kind": "fill_card", "card": "visa-4242", "site": "https://shop.example.test", "field": "number" },
    { "kind": "fill_card", "card": "visa-4242", "site": "https://shop.example.test", "field": "expiry" },
    { "kind": "fill_card", "card": "visa-4242", "site": "https://shop.example.test", "field": "cvc" }
  ]
}

Every call that types a saved card asks the person first, naming the card and the site (“Pay with Visa ·· 4242 on shop.example.test?”). Allow once is the only way to approve it, and a webhook run, which nobody can answer, is refused. One call types one card on one site, and the page browser types it only while the page is still on that origin, including a payment frame inside it. postal_code comes from the billing contact. Approving the card names no amount, so the click that places the order still asks, with the total the page shows; a call that types the card and presses that click asks once, showing both. Every run of the bot hides each saved card number from tool output, in plain, digits-only, spaced and dashed forms. The security code is not hidden, as for a wallet card: at 3 or 4 digits, hiding it would blank every total and order number that contains them.

Contacts

A contact is a name, address, phone or email, the user’s own or someone else’s, such as home, mom or office. A bot asks for one with request_secret and auth { "type": "contact" }, with no origin. A contact is not secret from the bot: list_secrets returns its fields, and the bot types them like any other text. It is still encrypted at rest.

Managing the vault

Owners can manage what is saved without asking the bot. Open Settings → Vault on web, the desktop app or mobile. It lists every item by its title, which the owner can change (a card without one shows as “Visa ·· 4242”, a login as its site), with a line from the item: a login’s username, a card’s expiry, a contact’s name and city. New adds a login, card, contact, API key or variable. Opening an item shows its fields; protected values show only after you confirm it’s you. From there you can edit an item, add a note on when the bot should use it, which list_secrets shows the bot, or remove it after confirming. Remove deletes the saved value immediately. Asking the bot to forget_secret a name only removes it; it does not list or replace one.

A sign-in without a password, such as an ID number and phone number, uses the same login card. CAPTCHA and passkeys still use request_takeover, as does any sign-in you prefer to do yourself.

One-time codes

When a site texts or emails you a code, the bot calls request_secret with oneTimeCode: { "origin": "https://www.example.com" } and shows a card, titled “Code for” that site’s host, for the code. Only the person the run belongs to can answer it. The code is 4 to 12 characters, letters and digits in groups joined by single dashes, spaces dropped; anything else is refused. It is kept encrypted for that run only, never saved to the vault, and the bot never sees it: it types it with browser_act fill_code into one field on the page. Like a login fill, the page browser types it only while the page is on that exact HTTPS origin, and only into a text, email or tel input that looks like a code box (it says so, takes digits, is named or labelled like a code, or holds 4 to 12 characters). The field is drawn as dots, and so is the slot widget around it that shows the digits, and its value stays out of snapshots; slots drawn more than 50 ms late, more than three levels up, or after the page remounted them may still show in a later screenshot, and the masking may also cover a container that shows the same characters elsewhere. Dashes are dropped for a numeric box. A code is used once: it is claimed just before the page is asked, so two fills never both get it, and a fill the page refused gives it back. After a fill the page took, the code is kept only so the run goes on hiding it, in the forms a page might draw it (one digit per line, spaced, or dashed), and it is deleted when the run ends. A wrong or expired code needs a new request, which shows a new card. A code box that refuses the fill, such as one input per digit, a password-type or number-type input, or a field in a frame from another site, falls back to request_takeover. The code-box check is broad on purpose: another field on the same site named like a code, such as a promo or postal code, qualifies too. This needs a computer image that includes the code fill; an older image reads the fill as a password and types only into password-type inputs on that origin, so it falls back to request_takeover for a code box.

The value is typed into a real browser on the bot’s computer. Anything that can read that browser, including the bot’s own shell commands through the page or its debugging port, could read a filled field. Keeping the value out of model input stops accidental disclosure, not a bot that sets out to extract it. Save logins only for accounts you would let the bot use unattended.