Browse docs
previewThese docs describe OpenIntents as it is being built. Endpoints and payloads may change before launch.

Browser sessions

A session opens a payer's browser profile without an intent, for you to drive step by step. Use one to sign a customer in to a shop once, so their later intents find them signed in, or to see what a profile holds. Nothing decides for you in a session: you send each step.

A session never pays and never places an order. Buying is what intents are for, with their spending caps and receipts.

Signing a customer in

Many shops want an account to check out, and their sign-in asks for things only your customer knows: a password, a code sent to their phone. The easy way is the sign-in link an intent waits with when the shop asks: you send it, your customer signs in on our page, and you build nothing. A session lets your app run the sign-in itself instead, and pass those on.

  1. Open a session on the customer's profile, at the shop's sign-in page:

    bash
    curl https://api.openintents.io/v1/sessions \
      -H "Authorization: Bearer $OPENINTENTS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "customer": "cus_42", "start_url": "https://www.example-shop.com/signin" }'

    It answers 201 with the session queued. It is open within seconds: follow its events or retrieve it.

  2. Look at the page with a snapshot. Each part you can act on has a ref, such as e7 for the email box.

  3. Act: type the email into e7, click Continue, type the password, then the code the shop sent. Show your customer the page with a screenshot and pass on what they type.

  4. Release the session. The profile keeps what the session changed: the customer is signed in for their next intents at that shop.

Your app builds the screen where the customer types; their password passes through your app on its way. Don't log it. OpenIntents keeps what a session types only until the command runs (see typed text).

The session object

json
{
  "id": "ses_51f0e2",
  "object": "session",
  "profile": "prf_7c1d04e2a9b3",
  "customer": "cus_42",
  "status": "open",
  "test": false,
  "start_url": "https://www.example-shop.com/signin",
  "url": "https://www.example-shop.com/signin",
  "title": "Sign in",
  "created_at": "2026-10-13T08:30:00Z",
  "opened_at": "2026-10-13T08:30:04Z",
  "expires_at": "2026-10-13T08:45:04Z",
  "closed_at": null,
  "close_reason": null
}
StatusMeaning
queuedWaiting for a browser.
openingThe browser is starting on the profile.
openReady for commands.
closingReleased (or at a limit): the browser closes and the profile is saved.
closedOver; close_reason says why.
failedOver, because its browser stopped; the profile keeps what was last saved.

close_reason is released, idle, timed_out, data_limit, browser_lost or error. Others may appear.

Commands

POST/v1/sessions/{id}/commands

Commands run one at a time, in the order sent. The request waits up to 20 seconds: 200 with the result, or 202 with the command still pending, which you follow with GET /v1/sessions/{id}/commands/{command_id}. A retry with the same Idempotency-Key returns the same command instead of running it again.

CommandBodyReturns
Navigate{ "type": "navigate", "url": "https://..." }The page's url and title.
Snapshot{ "type": "snapshot" }snapshot: the page's parts, each you can act on with a ref.
Act{ "type": "act", "ref": "e7", "action": "type", "text": "[email protected]" }The page after it.
Read{ "type": "read", "selector": "#order-history" }text: the page's text, or one element's.

act takes an action: click, type (with text), select (with value) or press (with key, such as Enter, Tab or Shift+Tab).

json
{
  "id": "cmd_9a2c",
  "object": "session_command",
  "type": "act",
  "status": "done",
  "url": "https://www.example-shop.com/signin/password",
  "title": "Sign in",
  "error": null,
  "created_at": "2026-10-13T08:30:12Z",
  "completed_at": "2026-10-13T08:30:13Z"
}

A few rules keep acts on the element you meant:

  • An act names a ref from the latest snapshot. After a navigate, and after any act but a type without line breaks, take a new snapshot first: the page may have changed under the old refs. Otherwise the act fails with not_found, as it does when the page moved on since the snapshot.
  • type goes only into text fields (not_actionable otherwise). A frame's elements have refs of their own: act on those, not on the frame.
  • text may hold line breaks but no other control characters, and key is one key, alone or with Shift or Control. Anything else answers 400.

What a session refuses

A command a session will not run comes back refused, with the reason in error. payment_not_allowed is for:

  • a control that places an order or pays (Place order, Pay, Buy now, Subscribe, Donate and the like), and on a checkout page also a bare Continue, Confirm, Submit or Next;
  • any action on a payment card's field;
  • typing what looks like a card number;
  • Enter, pressed or typed as a line break, at a checkout or where an order button shows.

A navigate goes only to public https URLs on the default port (url_not_allowed). Commands that could not run come back failed:

error.codeMeaning
not_foundNo such ref in the latest snapshot, or the page changed since.
not_actionableThe element can't take that action.
timed_outThe page took too long.
browser_errorThe browser could not do it.
browser_lostThe browser stopped; the session failed with it.
session_closedThe session ended before the command finished.

Screenshots

GET/v1/sessions/{id}/screenshot

A JPEG of the page, taken now. It shows the payer's signed-in pages, so it is sent Cache-Control: no-store and never kept. At most 2 screenshots of a session wait at once (429, too_many_commands).

Typed text

What a type command types (a password, say) is kept, sealed, only until the browser takes the command. Results and events never return it: events show typed_chars, how many characters were typed, and a snapshot never includes what text fields hold. The page itself may still show it, in a screenshot for example.

Limits

  • An account can have 2 sessions that have not ended at once (409, session_limit).
  • A session closes max_minutes after it opens (at most and by default 15), after 5 minutes with no command sent or run (idle), or once its pages use more data than a session may (data_limit). Screenshots and event streams don't keep it open.
  • At most 20 commands wait in a session at once (429, too_many_commands).

These may change.

Endpoints

POST/v1/sessions

profile or customer (not both; neither opens your account's own), start_url, max_minutes and webhook_url (an https URL for this session's events), all optional. 409 profile_busy when an intent or another session uses the profile.

GET/v1/sessions?status=open&customer=cus_42
GET/v1/sessions/{id}
POST/v1/sessions/{id}/release

Closes the browser and saves the profile: closing, then closed (released). Releasing one that already ended returns it unchanged.

GET/v1/sessions/{id}/events

The session's timeline, as a page or a live stream. Webhooks can tell you when a session opens, closes or fails.