Skip to content

Launch & run profiles

Before an agent can automate a page, the target profile must be running. This page walks the full lifecycle through the local daemon API — list profiles, create one (with fingerprint and proxy), launch it, poll its status, and stop it — and explains the PROFILE_NOT_RUNNING error you hit when you skip the launch step.

All requests go to the local daemon. The profile-management API lives under http://localhost:7891/api; the automation API (navigate, click, type, snapshot) lives under http://localhost:7891/api/automation/:profileId/<action> and is covered in HTTP API and the API reference.

Every daemon endpoint returns the same JSON envelope, so your code can branch on ok before reading data:

Success
{ "ok": true, "data": { } }
Failure
{ "ok": false, "error": { "code": "PROFILE_NOT_RUNNING", "message": "..." } }

Some endpoints return "data": null on success (launch, stop, unlock). Check ok, not the shape of data.

A profile reports one runtime status. Automation only works while the profile is running.

StatusMeaning
idleCreated but not launched. The browser process is not running.
launchingLaunch accepted; the browser is starting and syncing remote session state.
runningBrowser is up. Automation endpoints work.
stoppingStop accepted; cookies and tabs are syncing before the browser closes.
lockedThe profile is open on another device. You cannot launch it here until the lock is released.

Lists every profile with its current runtime status. No license is required to read the list.

List profiles with status
curl -s localhost:7891/api/profiles | jq '.data[] | {id, name, status}'

If you are an external agent that was not handed a specific profile ID, list profiles and let the operator pick one. Once you have an ID, keep it in an environment variable for the rest of the flow:

Pin the profile ID
PROFILE=<profile-id>

Creating a profile requires an active license. The body carries the profile name, proxy, fingerprint, and geolocation. On success the daemon returns 201 with the new profile and status: "idle".

Create a profile (fingerprint + proxy)
curl -s localhost:7891/api/profiles -X POST \
-H 'Content-Type: application/json' -d '{
"name": "My Profile",
"proxy": {"type": "none"},
"fingerprint": {
"os": "windows",
"userAgent": "Mozilla/5.0 ...",
"screenWidth": 1920, "screenHeight": 1080,
"language": "en-US", "timezone": "America/New_York",
"webrtc": "fake"
},
"geolocation": {"enabled": false}
}'

The webrtc field accepts real, fake, or disabled. To attach a proxy instead of {"type": "none"}, supply the connection details in the proxy object — see Configure proxies. For the full list of fingerprint fields and how they map to the browser, see Fingerprint parameters.

All fields are optional on update — send only what you want to change. The daemon returns the updated profile with its current status.

Rename and tag a profile
curl -s localhost:7891/api/profiles/$PROFILE -X PATCH \
-H 'Content-Type: application/json' -d '{"name": "New Name", "tags": ["work"]}'

Launch starts the browser process for a profile. It is asynchronous: on success you get 202 Accepted with "data": null and the status moves to launching.

Launch a profile
curl -s localhost:7891/api/profiles/$PROFILE/launch -X POST

Launch enforces two gates before it accepts:

  1. Active license. No active subscription → 403 with NO_SUBSCRIPTION (account never paid) or LICENSE_EXPIRED (subscription lapsed). Plans are paid-only — see Plans & pricing.
  2. A free concurrent slot. Your plan caps how many profiles can run at the same time — Starter 3, Pro 40, Agency 300. If you are already at the cap, launch returns 403 CONCURRENT_LIMIT_REACHED; stop a running profile or upgrade your plan first.
Error codeHTTPMeaning
NO_SUBSCRIPTION403The account has never had a paid plan.
LICENSE_EXPIRED403The subscription has lapsed.
CONCURRENT_LIMIT_REACHED403All concurrent slots for your plan are in use.
PROFILE_NOT_FOUND404No profile with that ID.
PROFILE_LOCKED409The profile is open on another device.
UNAUTHORIZED401The daemon’s session tokens are missing or expired; sign in via the app.

Because launch returns before the browser is ready, poll the status endpoint until it reads running. Do not send automation actions during launching.

Poll status after launch
curl -s localhost:7891/api/profiles/$PROFILE/status | jq -r .data.status

A simple wait loop:

Launch, then wait for running
curl -s localhost:7891/api/profiles/$PROFILE/launch -X POST
until [ "$(curl -s localhost:7891/api/profiles/$PROFILE/status | jq -r .data.status)" = "running" ]; do
sleep 1
done

The status endpoint is the single source of truth for whether a profile is automatable. It needs no license and returns instantly.

Check a single profile's status
curl -s localhost:7891/api/profiles/$PROFILE/status | jq -r .data.status
Response
{ "ok": true, "data": { "status": "running" } }

Stopping closes the browser. Before the process exits, the daemon syncs the session — open tabs and any changed cookies are uploaded so the next launch resumes where you left off. On success you get "data": null and the status moves to stopping, then back to idle.

Stop a profile
curl -s localhost:7891/api/profiles/$PROFILE/stop -X POST

If you call an automation endpoint (/api/automation/$PROFILE/navigate, /snapshot, /click, …) against a profile that is not running, the daemon responds with 404 and the error code PROFILE_NOT_RUNNING.

Automating a non-launched profile
{ "ok": false, "error": { "code": "PROFILE_NOT_RUNNING", "message": "..." } }

This is the most common first-run mistake: the profile exists, but its browser was never started (or was already stopped). Cookie endpoints behave the same way — GET/POST/DELETE /api/profiles/$PROFILE/cookies only work while the profile is running.

How to avoid it:

  • Launch the profile (POST /api/profiles/$PROFILE/launch) before any automation call.
  • Treat the 202 from launch as “accepted, not ready”. Poll GET /api/profiles/$PROFILE/status until it returns running — automating during launching can still fail.
  • Re-check status if a long-running flow stalls: a browser crash or a stop from another device flips the profile out of running.
  • Work strictly inside the profile the operator gave you. Do not launch, stop, or switch to a different profile to “fix” a mismatch — if the specified profile is not running or does not fit the task, stop and ask the operator.

A complete create → launch → automate → stop flow:

Full lifecycle
# 0. Pin the profile ID (or create one first)
PROFILE=<profile-id>
# 1. Launch (async — returns 202)
curl -s localhost:7891/api/profiles/$PROFILE/launch -X POST
# 2. Wait until the browser is up
until [ "$(curl -s localhost:7891/api/profiles/$PROFILE/status | jq -r .data.status)" = "running" ]; do
sleep 1
done
# 3. Automate (now safe — no PROFILE_NOT_RUNNING)
curl -s localhost:7891/api/automation/$PROFILE/navigate -X POST \
-H 'Content-Type: application/json' -d '{"url":"https://example.com"}'
# 4. Stop — syncs cookies & tabs before closing
curl -s localhost:7891/api/profiles/$PROFILE/stop -X POST