Integrations
Credential integrations let a browser session fill secrets — usernames, passwords, API keys — straight from your secret manager without the value ever reaching your automation code. Browserless resolves the reference server-side, types it into the target field, and then locks the session down so the filled secret can't be read back or captured.
1Password is the first supported provider. You connect a 1Password service account once, then reference items by their op://Vault/Item/field path from inside a session.
This is useful when:
- You need to log in with real credentials but don't want them in your code, environment variables, or logs.
- You rotate secrets in 1Password and want every session to use the current value automatically.
- You need an auditable record of every credential use, without the secret itself ever being recorded.
Authenticated Profiles replay a previously captured logged-in state. Integrations log in fresh each time by filling live credentials. Use a profile to skip the login; use an integration when you must actually authenticate with current secrets. The two can be combined.
- A Browserless cloud account
- A 1Password service account with read access to the vault/items you want to use
- A CDP client like Puppeteer or Playwright installed
How it works
An integration stores a 1Password service-account token (encrypted at rest) and an allow-list of domains, scoped to your API token. Inside a session you reference a secret by its op:// path and Browserless does the rest:
- Resolve — Browserless reads the referenced item from 1Password server-side using the stored service-account token.
- Fill — the resolved value is typed into the element you point at. The secret is never returned to your code, and never appears in CDP traffic, logs, or error messages.
- Lock down — once any secret has been filled, the session refuses to expose the page (see Security model), so the typed value can't be screenshotted or read back.
- Audit — the resolve is recorded against the integration: which
op://reference, on what origin, success or failure — but never the value.
Your automation only ever sends a reference (op://Vault/Item/field) and a target selector. The plaintext credential is resolved and typed inside Browserless; it never crosses back to the client.
Creating an integration
- Dashboard
- API
The quickest way is the Integrations page in your Browserless dashboard:
Create a 1Password service-account token
In 1Password, create a service account and grant it read access to only the vault(s) holding the items you'll reference. Scope it as narrowly as possible — the integration can resolve anything the service account can read. Copy the generated
ops_…token.Open Integrations
Go to Integrations in the dashboard sidebar and choose New Integration.
Add your service account
Paste the service-account token and a label to recognise it later. The token is encrypted at rest and is never shown again.
Set allowed domains
List the origins the integration is permitted to fill on (for example
https://app.example.com). A fill is only ever performed when the page's origin is on this list.
You can also create an integration over the REST API. The service-account token is encrypted at rest and is never returned by this or any other endpoint.
curl -X POST "https://production-sfo.browserless.io/integrations/onepassword?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"label": "acme-prod",
"serviceAccountToken": "ops_REPLACE_WITH_YOUR_SERVICE_ACCOUNT_TOKEN",
"allowedDomains": ["https://app.example.com"]
}'
The response is the integration record — note its id, you'll pass it when connecting:
{
"id": "op_int_0a1b2c3d4e5f6a7b8c9d0e1f",
"label": "acme-prod",
"kind": "service_account",
"allowedDomains": ["https://app.example.com"],
"connectUrl": null,
"expiresAt": null,
"lastResolvedAt": null,
"createdAt": "2026-06-18T12:00:00.000Z"
}
id— the integration identifier, passed as?integrationId=when connectingallowedDomains— normalised to origins; fills are only allowed on these- The response carries no token material — the service-account token can't be read back once stored
Filling a secret in a session
Connect with ?integrationId=<id>, navigate to a page on an allowed domain, then call the Browserless.loadSecret CDP method with the op:// reference and the field to fill.
Browserless.loadSecret is a CDP method, so use a CDP-capable client (Puppeteer, Playwright, or raw CDP).
- Puppeteer
- Playwright
- BrowserQL
import puppeteer from 'puppeteer-core';
const TOKEN = 'YOUR_API_TOKEN_HERE';
const INTEGRATION_ID = 'op_int_0a1b2c3d4e5f6a7b8c9d0e1f';
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://production-sfo.browserless.io/chromium?token=${TOKEN}&integrationId=${INTEGRATION_ID}`,
});
const page = await browser.newPage();
await page.goto('https://app.example.com/login');
const cdp = await page.createCDPSession();
// Resolve op://Vault/Item/field from 1Password and type it into the field.
await cdp.send('Browserless.loadSecret', {
ref: 'op://Acme/login/username',
targetSelector: '#email',
});
await cdp.send('Browserless.loadSecret', {
ref: 'op://Acme/login/password',
targetSelector: '#password',
});
await page.click('button[type="submit"]');
await page.waitForNavigation();
await browser.close();
import { chromium } from 'playwright-core';
const TOKEN = 'YOUR_API_TOKEN_HERE';
const INTEGRATION_ID = 'op_int_0a1b2c3d4e5f6a7b8c9d0e1f';
const browser = await chromium.connectOverCDP(
`wss://production-sfo.browserless.io/chromium?token=${TOKEN}&integrationId=${INTEGRATION_ID}`,
);
const page = browser.contexts()[0].pages()[0] ?? (await browser.contexts()[0].newPage());
await page.goto('https://app.example.com/login');
const cdp = await page.context().newCDPSession(page);
await cdp.send('Browserless.loadSecret', {
ref: 'op://Acme/login/username',
targetSelector: '#email',
});
await cdp.send('Browserless.loadSecret', {
ref: 'op://Acme/login/password',
targetSelector: '#password',
});
await page.click('button[type="submit"]');
await browser.close();
loadSecret is also a BrowserQL mutation. Pass integrationId on the endpoint, then resolve and fill within the same query — alias one call per field. The BQL mutation uses selector (the CDP method uses targetSelector).
curl -X POST "https://production-sfo.browserless.io/chromium/bql?token=YOUR_API_TOKEN_HERE&integrationId=op_int_0a1b2c3d4e5f6a7b8c9d0e1f" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation Login { goto(url: \"https://app.example.com/login\") { status } username: loadSecret(ref: \"op://Acme/login/username\", selector: \"#email\") { ok error } password: loadSecret(ref: \"op://Acme/login/password\", selector: \"#password\") { ok error } }"
}'
Each loadSecret field returns { ok, error }; the resolved value is never in the response. ref may be an op://Vault/Item/field reference; selector is optional (the focused element is used if omitted).
loadSecret returns { ok: true } on success, or { ok: false, error: '<code>' } on failure (it never throws). See Failure modes for each error code and how to handle it.
The field now holds the secret value, but your code never saw it. Submit the form as usual.
Security model
Once a secret has been filled in a session, Browserless treats the page as sensitive and disables every channel that could read the value back:
| Channel | Behaviour after a fill |
|---|---|
| Screenshots / PDFs / screencasts | Blocked (CaptureBlocked) |
| Live URLs (and their live-view input-event stream) | New connections refused; an already-streaming live view is torn down |
Page-content reads (BQL evaluate, html, text, querySelector, cookies) | Blocked (CaptureBlocked) |
| Session recording & replay (rrweb — captures DOM and input events) | Stopped on the first fill |
So the typed secret is never recorded by replay, streamed to a live viewer, or captured in an input-event log.
If you need a screenshot, PDF, or page content, take it before the first Browserless.loadSecret call. After a fill these are rejected for the rest of the session.
The lockdown is per session, so the cleanest way to get both secure login and unrestricted screenshots/PDFs is to combine integrations with Authenticated Profiles: in one session fill your credentials, sign in, then capture the signed-in state with Browserless.saveProfile. Every later session starts already logged-in via ?profile=<name> — no secret is filled there, so captures and page reads work normally.
// One-time: fill credentials with the integration, sign in, save the auth state.
const cdp = await page.createCDPSession();
await cdp.send('Browserless.loadSecret', { ref: 'op://Acme/login/username', targetSelector: '#email' });
await cdp.send('Browserless.loadSecret', { ref: 'op://Acme/login/password', targetSelector: '#password' });
await page.click('button[type="submit"]');
await page.waitForNavigation();
await cdp.send('Browserless.saveProfile', { name: 'acme-prod' });
// Everywhere after: reuse the profile — no fill, no lockdown, screenshots work.
// wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE&profile=acme-prod
This keeps secrets out of all but the initial login, and the integration stays the single source of truth when credentials rotate — just re-run the one-time step to refresh the profile.
Other guarantees:
- Domain allow-list — a fill is only performed when the page's origin is in the integration's
allowedDomains. - No leakage of the value — the plaintext is never returned to the client and is scrubbed from logs and error messages.
- Encrypted at rest — the service-account token is encrypted and never returned by any API.
- Audit log — every resolve is recorded with the
op://reference, target origin, and outcome — but never the secret value.
Failure modes
When loadSecret returns { ok: false, error }, error is one of these codes. Resolve/integration errors:
| Code | Meaning | What to do |
|---|---|---|
VaultUnreachable | Browserless couldn't reach 1Password (network/transport, or the service-account token couldn't authenticate). | Retry; if it persists, check 1Password's status and that the service-account token is valid. |
CredentialNotResolved | The op:// reference didn't resolve — wrong vault/item/field, or the service account can't read it. | Verify the reference and that the service account has access to that vault and item. |
DomainNotAllowed | The page's origin isn't in the integration's allowedDomains. | Add the origin to the integration's allowed domains. |
CredentialIntegrationExpired | The service-account token is expired or revoked. | Rotate the token in 1Password and update the integration. |
Fill-target errors (the credential resolved, but it couldn't be typed in):
| Code | Meaning | What to do |
|---|---|---|
SelectorNotFound | The selector (targetSelector over CDP) matched no element. | Check the selector and that the field has rendered. |
TargetNotFillable | The target isn't a fillable <input> / <textarea>. | Point at a fillable element. |
NoFocusedElement | No selector was given and nothing is focused. | Pass a selector, or focus an input first. |
AuditWriteFailed | The fill was refused because its audit record couldn't be written (fail-closed). | Retry; if it persists, contact support. |
Managing integrations
Integrations are managed through the same REST surface, scoped to your API token.
List integrations
curl "https://production-sfo.browserless.io/integrations/onepassword?token=YOUR_API_TOKEN_HERE"
Returns an array of integration records (no token material). Paginate with limit and offset.
Get one integration
curl "https://production-sfo.browserless.io/integrations/onepassword/op_int_0a1b2c3d4e5f6a7b8c9d0e1f?token=YOUR_API_TOKEN_HERE"
View the audit log
curl "https://production-sfo.browserless.io/integrations/onepassword/op_int_0a1b2c3d4e5f6a7b8c9d0e1f/audit?token=YOUR_API_TOKEN_HERE"
Returns one entry per credential resolve — the op:// reference, target origin and selector, and the outcome. Secret values are never stored. The dashboard's Activity view renders the same log.
Delete an integration
curl -X DELETE "https://production-sfo.browserless.io/integrations/onepassword/op_int_0a1b2c3d4e5f6a7b8c9d0e1f?token=YOUR_API_TOKEN_HERE"
Deletion is permanent; the encrypted service-account token is removed.
FAQ
Can my automation read the secret value?
No. You send a reference (op://Vault/Item/field) and a target selector; Browserless resolves and types the value inside the session. It is never returned to the client, and after the first fill the page-read and capture channels are disabled so it can't be extracted.
Why is my screenshot/evaluate returning CaptureBlocked?
Because a secret has already been filled in that session. Capture and page-content reads are disabled after the first Browserless.loadSecret to prevent the value from leaking. Run any captures before filling, or in a separate session.
Why did loadSecret return ok: false?
Common causes: the targetSelector didn't match an element on the page, the page's origin isn't in the integration's allowedDomains, or the op:// reference doesn't resolve for the service account (wrong vault/item/field, or the account lacks access).
How is this different from Authenticated Profiles?
A profile replays a previously captured logged-in state (cookies/storage). An integration logs in fresh by filling live credentials from 1Password. Use a profile to skip login entirely; use an integration when you must authenticate with current secrets — for example when the session needs to perform a real sign-in. They can be used together.
Further reading
- Authenticated Profiles — reuse a captured logged-in state instead of re-authenticating
- 1Password service accounts — create and scope the token this feature uses
- 1Password secret references — the
op://Vault/Item/fieldsyntax - Connection URL Patterns — full reference for connect-time query parameters