Docs/Integration Guide — zero to first task raw .md

Integration Guide — zero to first task

This is the developer front door — for a product that wants to run a browser action for its users. It walks you from nothing to one real action (here: a document upload into a third-party site) in your end user's own browser. ~30 minutes. The example below uses a site with no API, but the same flow works for any browser process.

Roles recap. You (the customer) supply the app + the signed playbooks. Your end user supplies the real browser + their login for the destination site. OpenErrand is the pipe. Nobody but the end user ever needs the destination credentials, and credentials never touch OpenErrand's servers. (Documents are intended to be sourced locally too — see the note in What flows where below.)

#0. Sign up and register your signing key

Sign up (or POST /signup with your email) to get, on the spot:

  • a tenant ID (e.g. acme);
  • a tenant API key — your server-side secret (request pairing tokens, run tasks, read audit).

Then generate your signing keypair and register the public half (you keep the private key):

npx @obep/cli keygen --out keys     # -> keys/tenant.pub, keys/tenant.key (keep secret)
curl -X POST $RELAY/signing-key \
  -H "Authorization: Bearer $API_KEY" -H "content-type: application/json" \
  -d "{\"publicKey\":\"$(cat keys/tenant.pub)\"}"

Tasks run against the managed relay endpoint (wss://relay.openerrand.app) — or self-host (SELF_HOSTING.md).

#1. Install

npm install @obep/sdk        # the client your app integrates
npm install -D @obep/cli     # the obep-playbook authoring CLI (or use npx)

#2. Author a playbook for the portal

A playbook is a signed, permission-fenced recipe for one portal flow. Most teams have their coding assistant draft it — see Author a playbook with an LLM coding tool. You can also record it once:

# In the OpenErrand side panel: "Record a playbook" -> do the flow -> "Stop & derive".
# Or derive from an observed-run trace:
npx @obep/cli derive portal-trace.json --out portal.json   # steps + tightest fence; secrets never captured
npx @obep/cli lint   portal.json                              # schema + danger flags
npx @obep/cli sign   portal.json --key keys/tenant.key --out portal-signed.json

Register portal-signed.json with the relay (via OpenErrand, or your own registry if self-hosting). It now has a playbookId you reference at runtime.

#3. Pair the user's browser to (tenant, user)

When the user logs into your app, your server mints a short-lived pairing token:

// your server, authenticated with your API key
const res = await fetch(`${RELAY_HTTP}/pairing-tokens`, {
  method: "POST",
  headers: { authorization: `Bearer ${API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({ userId: "dana" }),
});
const { pairingToken } = await res.json();   // -> binding (acme, dana) once approved

One-click handoff (recommended). Your app's page hands the token to the extension directly, so the user just clicks Connect in your app and Approve in the extension — no copy/paste. Add your origin to the extension's externally_connectable list, then from your page:

const OPENERRAND_EXTENSION_ID = "<published-extension-id>";
function connectOpenErrand(pairingToken) {
  if (!window.chrome?.runtime?.sendMessage) return false; // extension not installed / origin not allowlisted
  chrome.runtime.sendMessage(
    OPENERRAND_EXTENSION_ID,
    { type: "openerrand-pair", pairingToken, appName: "Acme" },
    (resp) => { if (resp?.received) toast("Approve the connection in OpenErrand"); },
  );
  return true;
}

The extension parks the request and shows the user “Allow Acme to run tasks in your browser?” — it never pairs silently. The token is single-use and relay-validated, the origin must be allowlisted, and the user approves: three independent gates.

If your page can't do the handoff, the user can paste the token into the side panel's Connect an app field as a fallback. Enterprise installs auto-pair via a tenant-signed identity assertion — see ENTERPRISE_DEPLOYMENT.md.

#4. The user stores their portal login (once)

In the OpenErrand side panel the user unlocks their vault (passphrase) and saves their carrier credentials. These are AES-GCM encrypted on their device, namespaced to the binding, and never sent to you or OpenErrand.

#5. Run the task

From your backend (the API key and your LLM stay server-side; the work happens in the user's browser):

import { RelayClient } from "@obep/sdk";
import { WebSocket } from "ws"; // Node; in the browser, omit WebSocketImpl

const client = new RelayClient({ url: RELAY_WS, apiKey: API_KEY, WebSocketImpl: WebSocket });

const run = client.run({
  url: "https://portal.example.com/login",
  userId: "dana",
  playbookId: "acme.portal-upload",
  decide,                       // your LLM — only used if a deterministic step breaks
});

for await (const status of run) updateUserUI(status.phase);   // live status stream
const { confirmationNumber } = await run;                     // result bag

A deterministic playbook needs no LLM for the happy path — the steps drive it, and no page content leaves the device. To back decide with a real model (Claude or otherwise) for the cold-start / fallback path, see the LLM decider quickstart.

#6. See what happened

curl -H "Authorization: Bearer $API_KEY" $RELAY_HTTP/audit/acme    # your tenant only (cross-tenant = 403)

Or use the OpenErrand dashboard. Audit records log that an action occurred (domain + hash, never content).

#What flows where (the part your security team will ask about)

Data Path
Portal credentials on-device vault → straight to the portal. Never to you or OpenErrand.
The document the user's browser → the portal. (Source it locally; don't route it through the relay.)
Page context (LLM mode only) minimized + redacted → your LLM, via the relay (which forwards, never stores). Deterministic mode sends none.
Status + audit relay → your app; audit is per-tenant and access-controlled.

Verify all of this yourself: read the open extension source and run the conformance suite against the relay. See SECURITY_MODEL.md.

#Next