Credential vault
Manage vault credentials directly from the Browser Agent API — the same vault the Console manages, scoped to your account. Store a login once, then reference it on runs with credential_id. Secret material is write-only: no endpoint ever returns a password or TOTP seed, in any response, error, or log.
Create
POST /api/v1/credentials — requires domain, username, password. Optional: totp_secret (base32 seed — OTP challenges are then answered in-process at run time), custody (defaults to trusted_runners — see Custody policies), mfa, source_id. Username, password, and TOTP seed are envelope-encrypted at rest (per-secret AES-256-GCM data key wrapped by Cloud KMS, automatic rotation); the response returns id + metadata only.
Create — field reference
POST/api/v1/credentials (createCredential)
Create a vault credential
Request body
| Field | Type | Req | Description |
|---|---|---|---|
| domain | string | yes | Login site domain, e.g. portal.example.com (scheme/path stripped). |
| username | string | yes | — |
| password | string | yes | Stored envelope-encrypted: per-secret AES-256-GCM data key wrapped by Cloud KMS (automatic key rotation). Never returned by any endpoint. |
| totp_secret | string | — | Optional base32 TOTP seed — OTP challenges are answered in-process at run time. Sets mfa: totp. |
| custody | string default "trusted_runners" | — | Custody policy: any | trusted_runners | deck_only | pinned:<runner>[,<runner>…]. See /docs/security/custody. |
| mfa | string none | totp | sms-relay | passkey | — | MFA mode; inferred as totp when totp_secret is set. |
| source_id | string | — | Optional catalog source id when the site comes from the sources list. |
Response
| Field | Type | Req | Description |
|---|---|---|---|
| id | string | — | cred_… — pass as credential_id on POST /v1/responses |
| object | "banana_peel.credential" | — | — |
| domain | string | — | — |
| source_id | string | — | — |
| username_masked | string | — | Masked username for display (e.g. jdo***@example.com). |
| mfa | string none | totp | sms-relay | passkey | — | — |
| custody | string | — | any | trusted_runners | deck_only | pinned:<runner>[,<runner>…] |
| health | string ok | stale | failed | — | — |
| created_at | string (date-time) | — | — |
| last_used | string (date-time) | — | — |
HTTP statuses
201— Created — id + metadata only (never the secret)400— Invalid request (`invalid_credential` for missing/oversized fields, `invalid_custody` for a malformed policy)401— Invalid API key
Generated from OpenAPI. Do not hand-maintain this table.
curl -s "$BANANA_PEEL_BASE/api/v1/credentials" \
-H "Authorization: Bearer $BANANA_PEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "portal.example-utility.com",
"username": "jdoe@example.com",
"password": "hunter2-rotate-me",
"totp_secret": "JBSWY3DPEHPK3PXP",
"custody": "trusted_runners"
}'
# → 201
# {
# "id": "cred_9f2ab41c77d0e83b4a51",
# "object": "banana_peel.credential",
# "domain": "portal.example-utility.com",
# "source_id": null,
# "username_masked": "jdo***@example.com",
# "mfa": "totp",
# "custody": "trusted_runners",
# "health": "ok",
# "created_at": "2026-08-13T14:00:00.000Z",
# "last_used": null
# }List / fetch
GET /api/v1/credentials lists your non-revoked credentials; GET /api/v1/credentials/:id fetches one. Both return metadata only: id, domain, masked username, mfa, custody, health, created_at, last_used.
Credential object
Vault credential metadata. Secret material (password / TOTP seed) is write-only and never appears here.
| Field | Type | Req | Description |
|---|---|---|---|
| id | string | — | cred_… — pass as credential_id on POST /v1/responses |
| object | "banana_peel.credential" | — | — |
| domain | string | — | — |
| source_id | string | — | — |
| username_masked | string | — | Masked username for display (e.g. jdo***@example.com). |
| mfa | string none | totp | sms-relay | passkey | — | — |
| custody | string | — | any | trusted_runners | deck_only | pinned:<runner>[,<runner>…] |
| health | string ok | stale | failed | — | — |
| created_at | string (date-time) | — | — |
| last_used | string (date-time) | — | — |
Generated from OpenAPI. Do not hand-maintain this table.
curl -s "$BANANA_PEEL_BASE/api/v1/credentials" \
-H "Authorization: Bearer $BANANA_PEEL_API_KEY"
# → { "object": "list", "data": [ /* metadata rows — never secrets */ ] }Use on a run
Pass the id as credential_id on POST /api/v1/responses. After custody is proven satisfiable, Banana Peel decrypts the secret and injects username / password (and optional TOTP) into the task the same way inline credentials would — onto whichever runner routing selects (smart, steel, browserbase, deck, …). Vault usage never pins routing to deck; when routing doesselect Deck (pinned, smart, race, or learning), the credential is additionally attached through Deck's native vault — registered on the run's source and reused across runs — because Deck gates login-walled sources on an attached credential rather than the instruction text. deck_only remains an opt-in custody policy; the create default is trusted_runners. If the vault credential (or request totp_secret) includes a base32 TOTP seed, OTP asks are answered in-process and the run does not pause. Username/password alone is not enough — the run still pauses for the code. When a target site asks for MFA, 2FA, or an OTP, the run pauses immediately and asks the user for the code. It does not keep trying other runners. See MFA / OTP and Custody policies.
Secrets ride the dispatch only — the persisted run record, the execution trace, and every API/webhook payload keep the pre-injection task text. If custody rules out every runner in the requested routing, the run fails free with 409 custody_unsatisfiable before anything dispatches or bills.
curl -s "$BANANA_PEEL_BASE/api/v1/responses" \
-H "Authorization: Bearer $BANANA_PEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "banana-peel",
"input": "Download the latest bill PDF from https://portal.example-utility.com",
"credential_id": "cred_9f2ab41c77d0e83b4a51",
"routing": "smart",
"background": true
}'Update
PATCH /api/v1/credentials/:id — change the custody policy, rotate the password, or set / replace / remove the TOTP seed (totp_secret: null removes it).
Update — field reference
PATCH/api/v1/credentials/{id} (updateCredential)
Update custody policy or rotate secrets
Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| id | path: string | yes | — |
Request body
| Field | Type | Req | Description |
|---|---|---|---|
| custody | string | — | any | trusted_runners | deck_only | pinned:<runner>[,<runner>…] |
| password | string | — | Rotate the stored password. |
| totp_secret | string | null | — | Replace the TOTP secret, or null to remove it. |
| mfa | string none | totp | sms-relay | passkey | — | — |
Response
| Field | Type | Req | Description |
|---|---|---|---|
| id | string | — | cred_… — pass as credential_id on POST /v1/responses |
| object | "banana_peel.credential" | — | — |
| domain | string | — | — |
| source_id | string | — | — |
| username_masked | string | — | Masked username for display (e.g. jdo***@example.com). |
| mfa | string none | totp | sms-relay | passkey | — | — |
| custody | string | — | any | trusted_runners | deck_only | pinned:<runner>[,<runner>…] |
| health | string ok | stale | failed | — | — |
| created_at | string (date-time) | — | — |
| last_used | string (date-time) | — | — |
HTTP statuses
200— Updated credential metadata400— Invalid field (`invalid_credential` / `invalid_custody`)404— Not found (`credential_not_found`) — unknown, revoked, or owned by another account
Generated from OpenAPI. Do not hand-maintain this table.
curl -s -X PATCH "$BANANA_PEEL_BASE/api/v1/credentials/cred_9f2ab41c77d0e83b4a51" \
-H "Authorization: Bearer $BANANA_PEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "custody": "pinned:browserbase,steel" }'Revoke
DELETE /api/v1/credentials/:id soft-deletes the credential: it disappears from lists and can no longer be used on runs.
curl -s -X DELETE "$BANANA_PEEL_BASE/api/v1/credentials/cred_9f2ab41c77d0e83b4a51" \
-H "Authorization: Bearer $BANANA_PEEL_API_KEY"
# → { "id": "cred_9f2ab41c77d0e83b4a51", "object": "banana_peel.credential", "deleted": true }Scoping & errors
- Credentials are scoped to the API key's account — exactly the set you see in Console → Vault. A credential owned by another account is indistinguishable from a nonexistent one: always
404 credential_not_found, never a 403. 400 invalid_credential— missing required fields, wrong types, or oversized values (domain ≤ 253, username ≤ 320, password ≤ 1024, totp_secret ≤ 128 characters). The offending value is never echoed back.400 invalid_custody— malformed custody policy. Valid:any|trusted_runners|deck_only|pinned:<runner>[,<runner>…].401 invalid_api_key— passAuthorization: Bearer bp_live_…orx-api-key.