Agent-readable docs index: /docs/llms.txt. Full docs in one file: /docs/llms-full.txt. Download /docs/docs.zip to grep all markdown files locally.

Website authentication

Authenticate into target websites during hosted Libretto Cloud runs.
This page is about signing into the websites your workflows automate. For signing into Libretto Cloud itself and issuing API keys, see Libretto Cloud authentication.
Hosted website authentication uses the same workflow code as local runs:
  • credentials declares which secrets the workflow can use for login.
  • authProfile declares the provider-native browser profile to reuse.
  • librettoAuthenticate checks whether the profile is signed in and runs sign-in if needed.
  • claimSmsOtp waits for a portal SMS one-time code on a tenant inbox number when the login texts MFA codes.
import { librettoAuthenticate, workflow } from "libretto"; export default workflow("portalReport", { startUrl: "https://portal.example.com", credentials: ["username", "password"], authProfile: { name: "portal", refresh: true, }, async handler(ctx, input) { await librettoAuthenticate(ctx, { credentials: input.credentials, isSignedIn: async ({ page }) => await page .getByRole("button", { name: /account|sign out/i }) .isVisible() .catch(() => false), signIn: async ({ page }, credentials) => { await page.goto("https://portal.example.com/login"); await page.getByLabel("Email").fill(credentials.username); await page.getByLabel("Password").fill(credentials.password); await page.getByRole("button", { name: /log in/i }).click(); }, }); }, });

Credentials in hosted runs

Hosted runs resolve declared workflow credentials from Libretto Cloud's encrypted credential store. They do not read credential values from the executor environment.
For local development, put matching LIBRETTO_CLOUD_ variables in .env:
LIBRETTO_CLOUD_USERNAME=alice@example.com LIBRETTO_CLOUD_PASSWORD=secret
Push non-empty LIBRETTO_CLOUD_ variables to Libretto Cloud:
npx libretto cloud credentials push

Profiles in hosted runs

Hosted profiles are provider-native browser profiles. They are not local .libretto/profiles/<name>.json files, and Libretto does not upload local profile files during deploy.
When you deploy a workflow that declares authProfile, Libretto Cloud registers the profile name if it does not already exist. When a hosted run starts, Libretto Cloud passes that profile name to the active browser provider.
If refresh: true is set, the hosted provider should persist browser state changes made during the run back to that profile. This lets a successful sign-in repair an expired profile for future runs.

Local profiles versus hosted profiles

Local profiles are Playwright storage-state JSON files under .libretto/profiles/. They are useful for local runs and local development.
Hosted profiles are managed by the browser provider selected by Libretto Cloud. They are useful for deployed jobs and local CLI runs that use --provider libretto-cloud.
The two are intentionally separate. To seed a hosted profile, run the workflow with Libretto Cloud and let sign-in create or refresh the provider-native profile.

SMS one-time codes

When a portal texts a one-time code, use Libretto Cloud SMS OTP instead of scraping an inbox or putting an API key in workflow source.
  1. Provision a tenant inbox number (libretto cloud sms-numbers provision --label <portal> or the dashboard Phone numbers page).
  2. Register that full phone number (with country code) as the MFA phone on the portal account. Prefer one number per portal.
  3. In the workflow, await claimSmsOtp before clicking send-code, then otp.wait() for the code.
Hosted jobs inject a short-lived job token automatically. Locally set LIBRETTO_API_KEY (same key as deploy and CLI).
import { claimSmsOtp, librettoAuthenticate, workflow } from "libretto"; export default workflow("portalReport", { startUrl: "https://portal.example.com", credentials: ["username", "password"], authProfile: { name: "portal", refresh: true }, async handler(ctx, input) { await librettoAuthenticate(ctx, { credentials: input.credentials, isSignedIn: async ({ page }) => await page .getByRole("button", { name: /account|sign out/i }) .isVisible() .catch(() => false), signIn: async ({ page }, credentials) => { await page.goto("https://portal.example.com/login"); await page.getByLabel("Email").fill(credentials.username); await page.getByLabel("Password").fill(credentials.password); await page.getByRole("button", { name: /log in/i }).click(); const otp = await claimSmsOtp({ phoneNumberLabel: "uhc" }); await page.getByRole("button", { name: /send code/i }).click(); const { code } = await otp.wait(); await page.getByLabel("Code").fill(code); await page.getByRole("button", { name: /verify/i }).click(); }, }); }, });