Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pinchtab/pinchtab/llms.txt

Use this file to discover all available pages before exploring further.

PinchTab’s multi-instance architecture enables running multiple isolated Chrome browsers simultaneously, each with their own cookies, history, profiles, and configurations.

Quick Start

1

Start the Orchestrator

The orchestrator runs on port 9867 and manages all instances.
pinchtab
2

Create Your First Instance

Create a headed instance (visible window) for interactive work:
curl -X POST http://localhost:9867/instances/launch \
  -H "Content-Type: application/json" \
  -d '{"mode":"headed"}'
Response:
{
  "id": "inst_a365262a",
  "profileId": "prof_7f3d1e8b",
  "profileName": "instance-1672531200",
  "port": "9868",
  "headless": false,
  "status": "starting",
  "startTime": "2026-03-01T06:30:00Z"
}
The instance gets:
  • Auto-allocated port (9868, 9869, …) - unique per instance
  • Hash-based ID (inst_XXXXXXXX) - stable and portable
  • Temporary profile (auto-generated, deleted on stop)
  • Chrome process - running independently on its port
  • Lazy initialization - Chrome starts on first request
3

Navigate and Interact

Navigate the instance via the orchestrator:
curl -X POST http://localhost:9867/instances/inst_a365262a/tabs/open \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'
Response:
{
  "id": "tab_19949f62",
  "url": "https://example.com",
  "title": "Example Domain"
}
Each navigation automatically creates a new tab. Use the id (tabId) for subsequent operations on that specific tab.
4

Get Page Content

Retrieve page snapshot using the tab ID:
TAB=$(curl -s -X POST http://localhost:9867/instances/inst_a365262a/tabs/open \
  -d '{"url":"https://example.com"}' | jq -r '.id')

curl http://localhost:9867/tabs/$TAB/snapshot
Or get just the text:
curl http://localhost:9867/tabs/$TAB/text
5

Stop Instance

Release the instance and its allocated port:
curl -X POST http://localhost:9867/instances/inst_a365262a/stop
Port 9868 becomes available for reuse by the next instance. The temporary profile is automatically deleted.

Common Workflows

Parallel Scraping with Multiple Instances

Create 5 headless instances for concurrent scraping:
#!/bin/bash

URLs=(
  "https://example.com/page1"
  "https://example.com/page2"
  "https://example.com/page3"
  "https://example.com/page4"
  "https://example.com/page5"
)

INSTANCES=()

echo "Creating 5 headless instances..."

# Create instances (in parallel for speed)
for i in {1..5}; do
  curl -s -X POST http://localhost:9867/instances/launch \
    -H "Content-Type: application/json" \
    -d '{"mode":"headless"}' > /tmp/inst_$i.json &
done
wait

# Parse instance IDs
for i in {1..5}; do
  ID=$(jq -r '.id' /tmp/inst_$i.json)
  INSTANCES+=("$ID")
  echo "Created instance $i: $ID"
done

# Wait for instances to initialize (lazy Chrome start)
sleep 2

echo "Navigating all instances concurrently..."

# Navigate all instances and capture tab IDs
TABS=()
for i in "${!INSTANCES[@]}"; do
  ID="${INSTANCES[$i]}"
  URL="${URLS[$i]}"

  RESPONSE=$(curl -s -X POST "http://localhost:9867/instances/$ID/tabs/open" \
    -H "Content-Type: application/json" \
    -d "{\"url\":\"$URL\"}")
  TAB_ID=$(echo "$RESPONSE" | jq -r '.id')
  TABS+=("$TAB_ID")
done

echo "All pages loaded. Getting snapshots..."

# Get snapshots using tab IDs
for i in "${!TABS[@]}"; do
  TAB="${TABS[$i]}"

  curl -s "http://localhost:9867/tabs/$TAB/snapshot" > "snapshot-$i.json" &
done
wait

# Cleanup
echo "Stopping instances..."
for ID in "${INSTANCES[@]}"; do
  curl -s -X POST "http://localhost:9867/instances/$ID/stop" &
done
wait

echo "Done! Scraped ${#URLS[@]} pages in parallel."
Benefits:
  • 5 headless instances = 5x faster scraping than sequential
  • No cookie/history interference between sites
  • Each instance gets isolated Chrome process and temporary profile
  • Ports automatically allocated (9868-9872) and reused on stop
  • All API calls go through orchestrator proxy for consistency

Mixed Headed/Headless Testing

Test web app with multiple instances in different modes:
echo "Creating mixed instance setup for testing..."

# Headed instance for interactive debugging
WORK=$(curl -s -X POST http://localhost:9867/instances/launch \
  -H "Content-Type: application/json" \
  -d '{"mode":"headed"}' | jq -r '.id')

# Headless instance for automated testing (faster)
TEST=$(curl -s -X POST http://localhost:9867/instances/launch \
  -H "Content-Type: application/json" \
  -d '{"mode":"headless"}' | jq -r '.id')

# Another headed instance with persistent profile for state preservation
pinchtab profile create testing-profile
PROFILE_ID=$(pinchtab profiles | jq -r '.[] | select(.name=="testing-profile") | .id')

DEBUG=$(curl -s -X POST http://localhost:9867/instances/start \
  -H "Content-Type: application/json" \
  -d '{"profileId":"'$PROFILE_ID'","mode":"headed"}' | jq -r '.id')

echo "Dev (headed):     $WORK (temporary profile, no persistence)"
echo "Test (headless):  $TEST (temporary profile, fast)"
echo "Debug (headed):   $DEBUG (persistent profile, state saved)"
You now have three independent instances:
  • dev instance - Visible window, temporary profile (good for quick testing)
  • test instance - Headless, temporary profile (best for automated tests, no display overhead)
  • debug instance - Visible window, persistent profile (login state preserved, can reconnect later)

Load Distribution with Headless Fleet

Run a fleet of headless instances for high-throughput work:
#!/bin/bash

FLEET_SIZE=10
INSTANCES=()

echo "Launching fleet of $FLEET_SIZE headless instances..."

# Create instances in parallel
for i in $(seq 1 $FLEET_SIZE); do
  curl -s -X POST http://localhost:9867/instances/launch \
    -H "Content-Type: application/json" \
    -d '{"mode":"headless"}' > /tmp/inst_$i.json &
done
wait

# Collect instance IDs
for i in $(seq 1 $FLEET_SIZE); do
  ID=$(jq -r '.id' /tmp/inst_$i.json)
  INSTANCES+=("$ID")
done

echo "Fleet ready: ${#INSTANCES[@]} instances allocated (ports 9868-9877)"
Performance:
  • 10 instances = 10x parallelism (10 concurrent navigations)
  • Round-robin distribution = balanced load across fleet
  • Each instance isolated = no resource contention
  • Easy to scale: increase FLEET_SIZE for more throughput
For 100 instances: Allocate 5-10 GB RAM + adjust ulimits. Monitor with curl http://localhost:9867/instances | jq 'length'

Instance Lifecycle

Port Allocation

Ports are automatically allocated from the configured range:
Default range: 9868-9968 (100 instances)
Configure via environment variables:
export INSTANCE_PORT_START=9900
export INSTANCE_PORT_END=10000
./pinchtab

Chrome Initialization

Chrome uses lazy initialization: it starts when first needed, not at instance creation.
POST /instances/launch Instance created immediately
 status: "starting" (orchestrator monitor probes /health)
 First /health probe triggers Chrome initialization (8-20 seconds)
 Chrome starts, TabManager initializes
 Monitor's health check succeeds
  ↓ status: "running" (instance ready for requests)
→ Ready for navigation requests
Why lazy init? Reduces memory overhead for ephemeral instances, initialization happens automatically on first request.
Wait for ready state:
# Create instance
INST=$(curl -s -X POST http://localhost:9867/instances/launch | jq -r '.id')

# Poll until running (Chrome will initialize during health checks)
while [ "$(curl -s http://localhost:9867/instances/$INST | jq -r '.status')" != "running" ]; do
  sleep 0.5
done

# Now safe to use
curl -X POST http://localhost:9867/instances/$INST/tabs/open \
  -d '{"url":"https://example.com"}'

Monitoring

List All Instances

curl http://localhost:9867/instances | jq .
Response:
[
  {
    "id": "inst_a365262a",
    "profileId": "prof_7f3d1e8b",
    "profileName": "instance-1672531200",
    "port": "9868",
    "headless": false,
    "status": "running",
    "startTime": "2026-03-01T06:30:00Z"
  },
  {
    "id": "inst_b471d93c",
    "profileId": "prof_5e2c7f1a",
    "profileName": "instance-1672531205",
    "port": "9869",
    "headless": true,
    "status": "running",
    "startTime": "2026-03-01T06:30:05Z"
  }
]

Dashboard Web UI

Open http://localhost:9867 in a browser to see:
  • Live instance list (real-time updates)
  • Instance status, port, mode, uptime
  • Profile information per instance
  • Tab count per instance
  • Quick actions (view logs, stop instance)

Best Practices

Use persistent profiles for important sessions:
# For agents that need persistent login state
pinchtab profile create linkedin-scraper

PROFILE_ID=$(pinchtab profiles | jq -r '.[] | select(.name=="linkedin-scraper") | .id')

# Start instance with profile
INST=$(curl -s -X POST http://localhost:9867/instances/start \
  -H "Content-Type: application/json" \
  -d '{"profileId":"'$PROFILE_ID'","mode":"headless"}' | jq -r '.id')

# Login once, reuse session on next run
Persistent profiles preserve cookies, history, login state across restarts.

Architecture

Orchestrator (port 9867)
├─ Instance manager: lifecycle (create, monitor, stop)
├─ Profile manager: persistent browser state directories
├─ Port allocator: assigns ports 9868-9968
└─ Dashboard UI: monitor instances, create/stop via web

Instance 1 (port 9868, temp profile)
├─ Bridge: HTTP API for this instance only
├─ Chrome: independent browser process (lazy-init)
├─ Profile: temporary, deleted on stop
└─ Tabs: multiple tabs in same browser

Instance 2 (port 9869, profile=work)
├─ Bridge: HTTP API for this instance only
├─ Chrome: independent browser process (lazy-init)
├─ Profile: persistent (work), survives restarts
└─ Tabs: multiple tabs in same browser
Each instance is completely isolated:
  • Separate port (9868-9968, auto-allocated)
  • Separate Chrome process (independent browser, no cross-contamination)
  • Separate profile (cookies, history, cache all isolated)
  • Separate HTTP API (Bridge server on instance port)
Orchestrator forwards requests from 9867/instances/{id}/* to the appropriate instance port for clean routing.