Profiles & personas
What a profile is made of: the fields you set, the identity drawn from its seed, which values are taken from your machine rather than invented, and what may be changed after creation.
A profile is one persistent identity: its own browser directory, its own fingerprint, optionally its own proxy. This page is what it is made of and what you may change.
Creating one
Only name is required. Everything else is either drawn from a seed or takes the daemon's default.
$ curl -X POST http://127.0.0.1:8787/v1/profiles \
-H "Authorization: Bearer $SCALEBROWSER_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "shop-01", "geo_mode": "follow_exit", "proxy_id": "px_7f3a"}'| Field | Set at create | Changed later | What it does |
|---|---|---|---|
name | required | yes | Yours. The only field with no effect on anything the page sees. |
proxy_id | optional | yes | Which stored proxy this profile leaves through. null means your own address. |
geo_mode | optional | yes | What happens when the persona and the exit disagree. Default follow_exit. |
expected_country | optional | yes | The country strict_expected measures against. |
persona_country | optional | no | The region the identity is drawn for. See What you may constrain. |
group_id | optional | yes | A flat label. See Groups, presets & bulk. |
enabled | defaults true | yes | A disabled profile is skipped by leases and bulk starts. |
seed | optional | no | Reproduce a known identity down to the byte. |
engine_version | optional | no | Pin a specific installed engine. |
seed and persona_country are fixed at create time. Neither can be changed afterwards, because the whole identity was drawn from them.
persona_country is refused on any value, echo included: it is never part of a response, so nothing can be echoing it back, and the whole identity is drawn from the seed. A region you want to change is not an edit, it is a different profile.
But a profile's region is not frozen. What moves it is the exit, not the field: give the profile a proxy that leaves in the country you want, and the next start rewrites timezone, language and voices to it. That is the same mechanism that gives a proxy-less profile your own country, and the reason the region field is a starting value rather than a setting.
The persona
Every profile carries a full identity, and the whole thing rides in the profile object under persona. It is dozens of fields: user agent, platform, screen, GPU, timezone, locale, fonts, audio properties, speech voices, client hints, media devices, permission defaults, network information, battery, and a platform authenticator.
The interesting question is not what is in there. It is where each value came from, because that is what decides whether a page can catch it.
Values taken from your machine
Anything a page can cross-check against the machine's own rendering is read off the host, not invented. There is no version of this that works by claiming.
| Axis | Why it cannot be invented |
|---|---|
| GPU vendor and renderer | The machine's own drawing answers back. A claimed card that does not draw the way that card draws is a contradiction found in one comparison. |
| Screen geometry | The desktop answers, in both directions: window size, available area, the taskbar's bite. |
| Device pixel ratio | Same. The display scale is measurable from inside the page. |
These are adopted at create and again at every launch, which is what makes a profile survive being moved to another machine of the same operating system: it heals rather than lying about the machine it is now on. Across operating systems it does not heal, and the launch is refused instead: see Tier.
The consequence is honest and worth stating: every profile on one host shares that host's screen and GPU. Within one machine those axes are linkable by design. The alternative is a claim the page can disprove, and a disprovable claim is worse than a shared truth.
Values drawn from the seed
Axes with no hardware feedback are sampled deterministically from the profile's seed: timezone, locale, font set, browser version, device memory, hardware concurrency, audio properties, voices, media devices, battery, network information.
Same seed, same values, every time. That is why a profile is the same machine at every start, and why a seed you kept lets you rebuild a profile you lost.
Values rewritten at launch
Under the default follow_exit, timezone and locale are rewritten to match the proxy exit's cohort. That happens at launch, so moving a profile to a proxy in another country does the right thing without you editing anything.
An exit the daemon cannot identify is left alone rather than half-rewritten. Half a rewrite is a contradiction the profile did not have before. Coherence & proxies is the page for that.
With no proxy the same thing happens against your own connection. The daemon resolves the address the browser really leaves through and rewrites timezone and locale to that country. It is the same rule, not an exception to it: a profile claiming Berlin from an address that geolocates to Vienna is a contradiction any site can check for free, and the timezone is the half we can fix.
This now happens when you create the profile, not only when you first start it. Before, a profile made without a proxy showed a random country in its identity panel until its first launch, and then changed under you. The lookup is capped at four seconds and simply skipped when it fails, so creating a profile offline still works and leaves the drawn identity alone.
What you may constrain
Almost nothing, and that is deliberate.
A caller may narrow an axis the launch does not overwrite, and only as a weight on an existing draw. Of the axes anyone asked for, exactly one qualifies: country. Set it as persona_country when you create one profile, or as a preset's constraints.country for a batch. Both go through the same check.
Choosing a region pins language, timezone and speech voices together, because all three come out of one row of the model. It does not pin anything else: two profiles created for the same region still differ on every other axis.
$ curl http://127.0.0.1:8787/v1/persona/constraints \
-H "Authorization: Bearer $SCALEBROWSER_BEARER_TOKEN"That endpoint answers with the country codes the embedded model actually carries locale rows for. Read it rather than writing your own list: the set is a property of a pinned dataset, and a hand-copied list would drift into offering a region the daemon then refuses with a 400 nobody can explain.
A region is a starting value, not a promise. Under the default geo_mode the profile takes the country it really exits in: its proxy's, or with no proxy your own connection's. So the region decides the identity of a profile you run through a matching proxy, or one you deliberately set geo_mode: "off" on. Choosing a region that the exit would overwrite is refused when you create it, with a message naming both countries, rather than accepted and quietly changed.
There is deliberately no prefill of your own country. It would be wrong twice over: the identity model draws only eleven countries while the exit rule can rewrite to seventeen, so a country like Austria is one you can be given but never pick; and the usual case is not your own country but your proxy's, which a prefill would make you clear every time. A profile with no proxy already ends up with your country without anyone choosing it.
Why the list is so short. An extra knob is an extra way for a fleet to look alike. Every constraint a caller can set is one more thing a hundred profiles created by the same script will share, and a shared unusual value is exactly what a fleet is caught by. An unconstrained draw stays byte-identical whether or not you passed a constraint.
The protection verdict
A profile that is listed or fetched carries a protection object beside its own fields:
{
"id": "pr_9c21e4",
"name": "shop-01",
"runtime_state": "stopped",
"protection": { "level": "…", "blocked": false, "reasons": [] }
}It is produced by the same functions the launch itself runs, so blocked: true means "this profile will refuse to start", not a second opinion that might disagree. The management app deliberately does not render it; the API carries it because a script that starts fifty profiles wants to know which two will fail before it tries.
Deleting one
DELETE /v1/profiles/:id removes the row and the profile's browser directory. A running browser is stopped first.
That takes every cookie, every stored session and every login inside it, and it is not recoverable.
An agent can do this too. profile.batch with action: "delete" is part of the management tool set, and the Windows desktop client ships that set enabled. It is the one destructive tool the app serves. A profile somebody is actively working is refused, but nothing else stands in the way: there is no confirmation step and no undo. See the tool set for how to serve a narrower catalog.
If you sync, deleting here also deletes the synced copy. See Profile sync.
Next
- Coherence & proxies: what is checked before a profile is allowed to start.
- Groups, presets & bulk: making fifty of these at once.
- Proxies & exits: the half of the identity that lives outside the browser.
- REST API: the endpoints behind this page.