Drıftstack docs
Docs

Saved credentials

Some tasks need a value you would rather not put in a message — a sign-in password, a one-time code or a similar value. Saved credentials let you store that value once on your account and refer to it afterwards by a handle. When a step needs it, Driftstack substitutes the real value into that step and nothing else — and only when the page is on a website you saved it for.

Do not save payment card numbers or card security codes as credentials. A value that looks like a payment card number — 13 to 19 digits, with or without spaces, dashes or dots between them, that do not begin with 0 and pass the card check digit — is refused when you save it, and nothing is stored. To have the agent type a card number, register it for the one session that needs it as a session secret: it is held in memory for that session only and never stored.

The value is write-only. You save it; you never read it back. No endpoint returns it, it is not shown in the dashboard, Driftstack does not write it to a log line, it is not in the live event stream, it is not in a recipe you export, and it is not in a step’s result. To change one, delete it and save a new one.

Each credential is locked to its websites

Like an entry in a password manager, every saved credential names the website or websites it is for — shop.example.com, say. You give at least one when you save it, and up to 10.

Immediately before a step types the credential, Driftstack asks the browser which page it is on at that moment and types the value only when that page is:

  • an https:// page, and
  • on one of the credential’s websites: the exact host, unless you chose “and its subdomains” when you saved it, in which case any subdomain of a listed website matches too (login.shop.example.com for shop.example.com). Any port matches.

“And its subdomains” cannot be chosen for a name that many unrelated websites share — a public suffix such as co.uk, or a hosting platform’s name such as github.io or vercel.app — because every name under it is someone else’s website. Save such a website by its exact host (you.github.io). Driftstack reads which names those are from the Public Suffix List.

Anywhere else the step is refused before anything is typed. The step fails with diagnosis.category credential_site_not_allowed, and its reason names the credential’s label and the page’s host — never the value — for example:

the saved credential "Shop password" was not typed: this page is on
evil.example.net, and it may only be typed on shop.example.com

The model reads that sentence too, so put nothing secret in a label.

The same refusal applies when the page’s address cannot be read (an unknown page is not an allowed one) and when the page is plain http://. The turn ends on a refused step rather than trying again.

A field inside an embedded frame — a card number field that a payment provider hosts on the shop’s page, say — is typed into that frame’s own page. So the frame’s website must be one the credential is saved for as well as the page’s, and so must every frame it sits inside: save the provider’s host as one of the credential’s websites. The browser is asked for the frame’s address just before the value is typed. A frame on another website, a frame whose address cannot be read or is the page’s own, and a frame that is no longer the one the plan named are all refused the same way, with a reason that names no address:

the saved credential "Card" was not typed: the field is inside an
embedded frame whose site is not one it may be typed on

Once the value is typed, the browser must also confirm it went into that frame. When it does not, the step fails with a reason that names no address and no value, the value is not typed again, and the turn ends on that step: look at the page before you send the task again.

The page is the one the browser reports, not the one your message or the plan names. A message, or a page, that tells the agent to type your credential on another website cannot get it typed there.

A website is a host name: shop.example.com, with no https://, no path and no port. example.com does not match shop.example.com, and shop.example.com does not match example.com. An address on an IP number, or a single-word host such as localhost, cannot be saved.

Deleting a credential takes effect at once, including in a task that is already running. Driftstack checks the credential is still saved — and where it may be typed — again at each step that types it, so a step after the deletion is refused with invalid_request and the reason “it no longer exists”.

How a saved credential is used

You reference a credential by writing its handle inside a placeholder in your task:

{{credential:cred_9f2c41ab7d6e4850bc13f07a5e9d2c88}}

What happens then:

  1. The model is told the handle, never the value. When it plans your task it is given the list of handles your account holds and told it does not have the values and must never ask for them.
  2. The plan carries the placeholder. The planned step reads "value": "{{credential:cred_…}}", and that is what is stored in the session transcript and what comes back in the step result. A step whose selector names a password, one-time-code or card field (#password), or one marked sensitive, comes back with sensitive: true and no value at all.
  3. The value is substituted at the last moment — after the website check above. Driftstack swaps in the real value as the step is sent to the browser, and marks that step as sensitive so the value is not typed with simulated human typos and not logged.

A handle that is not yours, or does not exist, fails the step before anything is sent. The step is reported as a failure naming the handle, and the literal {{credential:…}} text is never typed into the page — which would otherwise be accepted by the form, go green, and be rejected by the site for a reason nothing in your task explains.

A placeholder only works as the whole text of a type step. The step’s value must be the placeholder and nothing else, the way a password manager fills a whole field. A placeholder in a URL, a selector or anywhere else — or inside other text in a type step, such as https://elsewhere.example/?q={{credential:…}} or two placeholders side by side — fails the step before anything is typed: composed into other text, the value would be text the page can send anywhere.

What the model is shown after a value is typed

The placeholder keeps the value out of the plan. It cannot keep it off the page: once the value is typed into a field that shows what you type (not a password field), it is on the screen and in the page’s text. So once a saved credential has been typed in a chat, for the rest of that chat:

  • the model is shown no screenshot, and
  • the value is replaced by its placeholder in every text the model is shown — the text it reads from the page, the page’s address and title, extracted text, step results and the conversation so far. The value is recognised as typed; percent-encoded in either letter case, as an address or a submitted form carries it (a space as + or %20); HTML-escaped by name or by number; escaped as in a script string; and in any mix of those. A value made only of digits — a one-time code, or a card number registered as a session secret — is recognised however the page groups its digits: with spaces, dashes or dots between them, or none.
  • A value cut short is replaced too. Page text is shortened before the model sees it, and a cut can fall inside the value. The beginning of the value left at a cut — four characters or more, at the end of a text or a line — is replaced by the placeholder as well.
  • Everything the browser reports — about any step, including an error about the step that typed the value — has the value replaced by its placeholder before it is read, shortened or becomes a step’s reason or summary.

What this does not cover:

  • you, and anyone with access to the session. This protects what the model is shown, nothing else. A value typed into a field that shows what you type is on the page, and the session’s live view, its screenshots and captures, and page reads through the API show the page as it is;
  • a page that shows the value changed — part of it other than its beginning, in different letter case, or encoded some other way — is not recognised, and fewer than four leading characters left at a cut are left as they are;
  • a value a website shows on its own (an account page that displays an API token, say), without it having been typed in that chat, is page text like any other;
  • once you delete a credential, Driftstack no longer has its value, so a later turn of the chat cannot recognise it on the page (screenshots stay off for that chat all the same);
  • the browser’s address is checked one round trip before the value is typed, so a page that navigates itself away in that instant is not seen;
  • a field inside an embedded frame is found by the frame’s position on the page, and the frame’s website is checked one round trip before the value is typed. A page that adds or removes a frame in that instant can put a different frame in that position, and the value is typed there if that frame has a matching field.

Limits

Credentials per account 50
Label 1–120 characters
Value 1–4096 bytes of UTF-8
Websites per credential 1–10 host names, 253 characters each

How it is stored

The value is encrypted at rest with AES-256-GCM under a key derived for your account, and bound to the individual record — its id, its handle and its websites — with the same mechanism and the same key hierarchy as your proxy secrets. A stored value cannot be decrypted with another account’s key, it cannot be moved to another record of your own, and a record whose websites were changed without the key no longer decrypts at all.

Retention

Deleting a credential removes the row and the stored value immediately; there is nothing to restore. If you close your account, saved credentials are deleted within 30 days, under the Privacy Policy §9 line for Customer-Provided Secrets; copies in encrypted backups are gone within a further 40 days.

Saving and deleting a credential

You save and delete credentials in the Driftstack dashboard, on the Saved credentials page, signed in: a label, the value, and the website or websites it is for. The API can list them but cannot save or delete one: a leaked API key must not be able to plant a credential that your later agent tasks would then use. To change a value or its websites, delete the credential and save a new one. A program that holds a value itself registers it for one session instead, as a session secret.

Only the account owner saves and deletes credentials, from their own workspace. A save or a delete sent with X-Driftstack-Account naming another account is refused with 400, before anything is read or written.

In a team workspace

Agent tasks a team member starts in the account owner’s workspace use the owner’s saved credentials. So a member working there — either role, member or admin — lists the owner’s credentials, by sending the X-Driftstack-Account header naming the owner:

GET /v1/account/me/credentials
Authorization: Bearer ds_live_<member-key>
X-Driftstack-Account: acc_<owner-uuid>

The answer is the same five fields the owner sees — handle, label, websites, include_subdomains and created_at — and never a value. A member can write a handle into a task in that workspace; only the owner can save or delete one. On the dashboard’s Saved credentials page, the owner’s workspace shows the owner’s list, read-only.

List

GET /v1/account/me/credentials

Requires a key with the read scope. Returns the handle, the label, the websites and when each was saved — never the values. Listing is the only saved-credential operation available through the API. With X-Driftstack-Account, a team member lists the owner’s credentials (see In a team workspace).

{
  "data": [
    {
      "handle": "cred_9f2c41ab7d6e4850bc13f07a5e9d2c88",
      "label": "Shop password",
      "sites": ["shop.example.com"],
      "include_subdomains": false,
      "created_at": "2026-10-01T09:14:22.000Z"
    }
  ]
}
Status Meaning
200 The saved credentials of the account, or of the team owner you work for.
403 X-Driftstack-Account names an account you are not a team member of.
503 This deployment is not configured to store credentials.