Core Concepts
This document describes the concepts that are implemented today in PinchTab.
Server
The server is the main PinchTab process.
Start it with:
pinchtab
# or explicitly
pinchtab server
What the server does:
- exposes the main HTTP API and dashboard on port
9867by default - manages profiles and instances
- proxies tab-scoped requests to the correct managed instance
- can expose shorthand routes such as
/navigate,/snapshot, and/action
Important clarification:
- the server is the public entry point
- for managed instances, the server usually does not talk to Chrome directly
- instead, it spawns or routes to a per-instance bridge process
Bridge
The bridge is the single-instance runtime.
Start it directly only when you want one standalone browser runtime:
pinchtab bridge
What the bridge does:
- owns exactly one Chrome browser process
- exposes browser and tab endpoints such as
/navigate,/snapshot,/action, and/tabs/{id}/... - is the process the server launches for each managed instance
In normal multi-instance usage, you usually interact with the server, not with bridge processes directly.
Profiles
A profile is a Chrome user data directory.
It stores persistent browser state such as:
- cookies
- local storage
- cache
- browsing history
- extensions
- saved account state
Profile facts that match the current implementation:
- profiles are persistent on disk
- profiles can exist without any running instance
- at most one active managed instance can use a given profile at a time
- profile IDs use the format
prof_XXXXXXXX GET /profileshides temporary auto-generated profiles unless you pass?all=true
Create a profile with the API:
curl -X POST http://localhost:9867/profiles \
-H "Content-Type: application/json" \
-d '{
"name": "work",
"description": "Main logged-in work profile"
}'
# Response
{
"status": "created",
"id": "prof_278be873",
"name": "work"
}
Instances
An instance is a managed browser runtime.
In practice, one instance means:
- one bridge process
- one Chrome process
- zero or one profile
- one dedicated port
- many tabs
Instance facts that match the current implementation:
- instance IDs use the format
inst_XXXXXXXX - ports are auto-allocated from
9868-9968by default - instance status is tracked as
starting,running,stopping,stopped, orerror - one profile cannot be attached to multiple active managed instances at the same time
Persistent vs temporary instances
There are two common ways to start an instance:
- with a named profile
- without a profile ID
If you start an instance with a profile ID, the instance uses that persistent profile.
If you start an instance without a profile ID, PinchTab creates an auto-generated profile named like instance-....
That temporary profile is deleted when the instance stops.
So this is the correct mental model:
- instances without an explicit profile are ephemeral
- the implementation still creates a temporary profile directory behind the scenes
- that temporary profile is cleanup state, not a reusable long-term profile
Starting an instance
Preferred endpoint:
curl -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" \
-d '{
"profileId": "prof_278be873",
"mode": "headed"
}'
# CLI Alternative
pinchtab instance start --profileId prof_278be873 --mode headed
# Response
{
"id": "inst_0a89a5bb",
"profileId": "prof_278be873",
"profileName": "work",
"port": "9868",
"headless": false,
"status": "starting"
}
Tabs
A tab is a single page inside an instance.
Tabs belong to an instance, and therefore inherit that instance's profile state.
What a tab gives you:
- its own URL and page state
- a snapshot of the accessibility tree
- action execution such as click, type, fill, hover, and press
- text extraction, screenshots, PDF export, cookie access, and evaluation
Open a tab in a specific instance:
INST=inst_0a89a5bb
curl -X POST http://localhost:9867/instances/$INST/tabs/open \
-H "Content-Type: application/json" \
-d '{"url":"https://pinchtab.com"}'
# Response
{
"tabId": "CDP_TARGET_ID"
}
Then use tab-scoped endpoints:
TAB=CDP_TARGET_ID
curl http://localhost:9867/tabs/$TAB/snapshot
curl -X POST http://localhost:9867/tabs/$TAB/action \
-H "Content-Type: application/json" \
-d '{"kind":"click","ref":"e5"}'
curl -X POST http://localhost:9867/tabs/$TAB/close
Are tabs persistent?
Usually, no.
For managed instances started by the server:
- tabs are runtime objects
- tabs disappear when the instance stops
- profiles persist, but open tabs do not
That means the persistent part is the profile state, not the tab list.
Element references
Snapshots return element references such as e0, e1, e2, and so on.
These refs are useful because they let you interact with elements without writing CSS selectors for common flows.
Relationships
The implementation is easiest to understand with these rules:
| Relationship | What is true today |
|---|---|
| Server -> Instances | One server can manage many instances |
| Bridge -> Chrome | One bridge owns one Chrome process |
| Instance -> Profile | An instance has zero or one profile |
| Profile -> Instance | A profile can have zero or one active managed instance at a time |
| Instance -> Tabs | An instance can have many tabs |
| Tab -> Instance | Every tab belongs to exactly one instance |
| Tab -> Profile | A tab inherits the instance profile, if one exists |
Profiles are reusable persistent state. Instances are temporary runtimes that may use a profile.
Shorthand routes vs explicit routes
PinchTab exposes two styles of interaction:
Explicit routes
These always name the resource you want:
POST /instances/startPOST /instances/{id}/tabs/openGET /tabs/{id}/snapshotPOST /tabs/{id}/action
This is the clearest model for multi-instance work.
Shorthand routes
These omit the instance and sometimes the tab:
POST /navigateGET /snapshotPOST /actionGET /text
These route to the "current" or first running instance.
Recommended mental model
For most users, this is the right sequence:
- start the server with
pinchtab - create a profile if you need persistence
- start an instance from that profile
- open one or more tabs in that instance
- snapshot a tab
- act on refs from that snapshot
If you do not need persistence:
- start an instance without
profileId - use it normally
- stop it when done
- let PinchTab delete the temporary profile automatically
Example workflows
Workflow 1: persistent logged-in browser
PROFILE_ID=$(curl -s -X POST http://localhost:9867/profiles \
-H "Content-Type: application/json" \
-d '{"name":"work"}' | jq -r '.id')
INST=$(curl -s -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" \
-d "{\"profileId\":\"$PROFILE_ID\",\"mode\":\"headed\"}" | jq -r '.id')
TAB=$(curl -s -X POST http://localhost:9867/instances/$INST/tabs/open \
-H "Content-Type: application/json" \
-d '{"url":"https://pinchtab.com/login"}' | jq -r '.tabId')
curl http://localhost:9867/tabs/$TAB/snapshot
Use this when you want cookies and account state to survive instance restarts.
Workflow 2: disposable run
INST=$(curl -s -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" \
-d '{"mode":"headless"}' | jq -r '.id')
TAB=$(curl -s -X POST http://localhost:9867/instances/$INST/tabs/open \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}' | jq -r '.tabId')
curl http://localhost:9867/tabs/$TAB/text
curl -X POST http://localhost:9867/instances/$INST/stop
Use this when you want a clean, throwaway session.
Summary
The durable object in PinchTab is the profile. The runtime object is the instance. The page object is the tab. The server manages them, and the bridge executes them.