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.
This guide covers proven patterns for common browser automation tasks using PinchTab’s multi-instance architecture.
Pattern 1: Headed vs Headless
Headed Mode (Visible Window)
Use when you need to see what’s happening :
curl -X POST http://localhost:9867/instances/launch \
-H "Content-Type: application/json" \
-d '{"mode":"headed"}'
Use cases:
Interactive debugging and testing
Visual QA/regression testing
Manual workflow verification
Step-by-step debugging
Learning/exploration
Characteristics:
Chrome window opens and is visible
Slightly slower (rendering overhead)
More memory (~100-150 MB)
Can interact manually if needed
Good for dev/test environments
Interactive Testing
Python Example
#!/bin/bash
# Create a headed instance for manual testing
INST = $( curl -s -X POST http://localhost:9867/instances/launch \
-H "Content-Type: application/json" \
-d '{"mode":"headed"}' | jq -r '.id' )
echo "Manual testing instance: $INST "
echo "Chrome window is open - interact manually or use API calls"
# Wait for Chrome to initialize
sleep 2
# Optionally navigate to starting URL
curl -s -X POST "http://localhost:9867/instances/ $INST /tabs/open" \
-H "Content-Type: application/json" \
-d '{"url":"https://app.example.com"}'
echo "Navigate the app manually, then run automated tests..."
sleep 300
# Keep instance running for 5 minutes
curl -X POST "http://localhost:9867/instances/ $INST /stop"
Headless Mode (Background)
Use when you need speed and efficiency :
curl -X POST http://localhost:9867/instances/launch \
-H "Content-Type: application/json" \
-d '{"mode":"headless"}'
Use cases:
Automated testing (CI/CD)
Data scraping
High-throughput operations
Batch processing
Production workloads
Characteristics:
No visible window (runs in background)
Faster execution (~20% speedup)
Less memory (~50-80 MB)
No manual interaction
Perfect for automation
Headless Pipeline
TypeScript Example
#!/bin/bash
# Create 5 headless instances for parallel testing
INSTANCES = ()
for i in { 1..5} ; do
INST = $( curl -s -X POST http://localhost:9867/instances/launch \
-H "Content-Type: application/json" \
-d '{"mode":"headless"}' | jq -r '.id' )
INSTANCES += ( " $INST " )
done
echo "Created 5 parallel test workers"
# Run tests in parallel
TEST_URLS = (
"https://app.example.com/test1"
"https://app.example.com/test2"
"https://app.example.com/test3"
"https://app.example.com/test4"
"https://app.example.com/test5"
)
for i in "${ ! INSTANCES [ @ ]}" ; do
INST = "${ INSTANCES [ $i ]}"
URL = "${ TEST_URLS [ $i ]}"
# Run test in parallel
(
curl -s -X POST "http://localhost:9867/instances/ $INST /tabs/open" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" $URL \" }" > /dev/null
echo "Test $i completed"
) &
done
wait
# Cleanup
for INST in "${ INSTANCES [ @ ]}" ; do
curl -s -X POST "http://localhost:9867/instances/ $INST /stop" > /dev/null
done
echo "Tests complete"
Pattern 2: Load Distribution
Round-Robin Distribution
Distribute requests evenly across instances:
#!/bin/bash
# Create 5 instances
INSTANCES = ()
for i in { 1..5} ; do
INST = $( curl -s -X POST http://localhost:9867/instances/launch \
-H "Content-Type: application/json" \
-d '{"mode":"headless"}' | jq -r '.id' )
INSTANCES += ( " $INST " )
done
# Wait for Chrome initialization
sleep 3
URLs = (
"https://example.com/page1"
"https://example.com/page2"
"https://example.com/page3"
"https://example.com/page4"
"https://example.com/page5"
"https://example.com/page6"
"https://example.com/page7"
"https://example.com/page8"
)
# Distribute evenly
for i in "${ ! URLS [ @ ]}" ; do
URL = "${ URLS [ $i ]}"
INST = "${ INSTANCES [$(( i % ${ # INSTANCES [ @ ]}))]}"
curl -s -X POST "http://localhost:9867/instances/ $INST /tabs/open" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" $URL \" }" &
done
wait
Each instance handles roughly equal work with round-robin distribution.
Queue-Based Distribution
Process a work queue dynamically:
#!/bin/bash
# Create instance pool (3 workers)
INSTANCES = ()
for i in { 1..3} ; do
INST = $( curl -s -X POST http://localhost:9867/instances/launch \
-H "Content-Type: application/json" \
-d '{"mode":"headless"}' | jq -r '.id' )
INSTANCES += ( " $INST " )
done
# Work queue (100 URLs)
QUEUE = ($( seq 1 100 | xargs -I {} echo "https://example.com/{}"))
# Process queue with max concurrent jobs
MAX_JOBS = 3
CURRENT_JOB = 0
# Start initial batch
for i in $( seq 0 $(( MAX_JOBS - 1 ))); do
[ $CURRENT_JOB -lt ${ # QUEUE [ @ ]} ] || break
URL = "${ QUEUE [ $CURRENT_JOB ]}"
INST = "${ INSTANCES [$((CURRENT_JOB % MAX_JOBS))]}"
(
curl -s -X POST "http://localhost:9867/instances/ $INST /tabs/open" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" $URL \" }" > /dev/null
echo "Processed: $URL "
) &
(( CURRENT_JOB ++ ))
done
# Process remaining queue
while [ $CURRENT_JOB -lt ${ # QUEUE [ @ ]} ]; do
URL = "${ QUEUE [ $CURRENT_JOB ]}"
INST = "${ INSTANCES [$((CURRENT_JOB % MAX_JOBS))]}"
(
curl -s -X POST "http://localhost:9867/instances/ $INST /tabs/open" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" $URL \" }" > /dev/null
echo "Processed: $URL "
) &
# Wait for a background job to complete
wait -n
(( CURRENT_JOB ++ ))
done
wait
echo "Queue processing complete"
Pattern 3: State Isolation
Per-User Profiles
Each user gets their own instance with persistent state:
#!/bin/bash
# Create persistent profiles for each user
pinchtab profile create user-1
pinchtab profile create user-2
pinchtab profile create user-3
# Get profile IDs
U1_PROF = $( pinchtab profiles | jq -r '.[] | select(.name=="user-1") | .id' )
U2_PROF = $( pinchtab profiles | jq -r '.[] | select(.name=="user-2") | .id' )
U3_PROF = $( pinchtab profiles | jq -r '.[] | select(.name=="user-3") | .id' )
# Create instances with profiles
U1 = $( curl -s -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" \
-d '{"profileId":"' $U1_PROF '","mode":"headless"}' | jq -r '.id' )
U2 = $( curl -s -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" \
-d '{"profileId":"' $U2_PROF '","mode":"headless"}' | jq -r '.id' )
U3 = $( curl -s -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" \
-d '{"profileId":"' $U3_PROF '","mode":"headless"}' | jq -r '.id' )
# Each user has:
# - Separate Chrome process
# - Persistent profile (survives restarts)
# - Separate cookies (login sessions preserved)
# - Separate history, local storage
# - No interference between users
Persistent profiles preserve cookies, cache, and history across instance restarts.
Pattern 4: Batch Operations
Sequential Processing
Simple one-at-a-time processing:
ITEMS = ( "item1" "item2" "item3" "item1000" )
for ITEM in "${ ITEMS [ @ ]}" ; do
curl -s -X POST "http://localhost:9868/navigate" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" https://api.example.com?item= $ITEM \" }"
sleep 1 # Rate limiting
done
Pros: Simple, easy to debug
Cons: Slow (1 item/sec = 1000 seconds for 1000 items)
Parallel Processing
Use multiple instances for speed:
INSTANCES = ()
# Create 10 instances
for i in { 1..10} ; do
INST = $( curl -s -X POST http://localhost:9867/instances/launch \
-H "Content-Type: application/json" \
-d "{ \" name \" : \" batch-worker- $i \" , \" headless \" :true}" | jq -r '.id' )
PORT = $( curl -s http://localhost:9867/instances/ $INST | jq -r '.port' )
INSTANCES += ( " $INST : $PORT " )
done
# Process items in parallel
ITEM_INDEX = 0
for ITEM in "${ ITEMS [ @ ]}" ; do
IFS = : read INST PORT <<< "${ INSTANCES [$((ITEM_INDEX % 10))]}"
curl -s -X POST "http://localhost: $PORT /navigate" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" https://api.example.com?item= $ITEM \" }" &
(( ITEM_INDEX ++ ))
done
wait
Performance: 1000 items with 10 instances = 100 seconds (10x faster!)
Pattern 5: Error Handling
Health-Check Before Operations
function safe_navigate () {
local PORT = $1
local URL = $2
# Check health
if ! curl -s "http://localhost: $PORT /health" | jq -e '.status == "ok"' > /dev/null ; then
echo "ERROR: Instance on port $PORT not healthy"
return 1
fi
# Navigate
curl -s -X POST "http://localhost: $PORT /navigate" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" $URL \" }"
}
safe_navigate 9868 "https://example.com" || echo "Navigation failed"
Timeout Handling
function navigate_with_timeout () {
local PORT = $1
local URL = $2
local TIMEOUT = $3
timeout $TIMEOUT curl -s -X POST "http://localhost: $PORT /navigate" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" $URL \" }"
if [ $? -eq 124 ]; then
echo "ERROR: Navigation timed out after ${ TIMEOUT }s"
return 1
fi
}
navigate_with_timeout 9868 "https://slow-site.com" 30
Graceful Degradation
function navigate_with_fallback () {
local URL = $1
# Try instance 1
if curl -s "http://localhost:9868/health" | jq -e '.status == "ok"' > /dev/null ; then
curl -s -X POST "http://localhost:9868/navigate" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" $URL \" }"
return 0
fi
# Fall back to instance 2
if curl -s "http://localhost:9869/health" | jq -e '.status == "ok"' > /dev/null ; then
echo "Instance 1 down, using fallback (9869)"
curl -s -X POST "http://localhost:9869/navigate" \
-H "Content-Type: application/json" \
-d "{ \" url \" : \" $URL \" }"
return 0
fi
# All instances down
echo "ERROR: All instances down"
return 1
}
Pattern 6: Monitoring & Cleanup
Active Instance Monitoring
#!/bin/bash
while true ; do
echo "=== $( date ) ==="
# Get all instances
INSTANCES = $( curl -s http://localhost:9867/instances )
# Count by status
RUNNING = $( echo " $INSTANCES " | jq '[.[] | select(.status=="running")] | length' )
STOPPED = $( echo " $INSTANCES " | jq '[.[] | select(.status=="stopped")] | length' )
TOTAL = $( echo " $INSTANCES " | jq 'length' )
echo "Total: $TOTAL | Running: $RUNNING | Stopped: $STOPPED "
# List each instance
echo " $INSTANCES " | jq '.[] | "\(.id) (\(.profileName)) - port \(.port) - \(.status)"'
sleep 30
done
Resource Monitoring
#!/bin/bash
while true ; do
echo "=== Instance Resource Usage ==="
# Get active instances
INSTANCES = $( curl -s http://localhost:9867/instances | jq -r '.[] | select(.status=="running") | .id' )
# Rough estimate: each headless ~60MB, headed ~120MB
HEADLESS = $( curl -s http://localhost:9867/instances | jq '[.[] | select(.headless==true)] | length' )
HEADED = $( curl -s http://localhost:9867/instances | jq '[.[] | select(.headless==false)] | length' )
EST_MEMORY = $(( ( HEADLESS * 60 ) + (HEADED * 120 ) ))
echo "Headless instances: $HEADLESS (~${ HEADLESS }*60MB = $(( HEADLESS*60 ))MB)"
echo "Headed instances: $HEADED (~${ HEADED }*120MB = $(( HEADED*120 ))MB)"
echo "Estimated memory: ~${ EST_MEMORY }MB"
# Alert if over limit
if [ $EST_MEMORY -gt 8000 ]; then
echo "⚠️ WARNING: High memory usage!"
fi
sleep 60
done
Summary
Pattern Use Case Throughput Memory Headed Interactive debugging Low High Headless Automation, scraping Medium Low Round-robin Balanced load Medium-High Medium Queue-based Dynamic work distribution High Low-Medium Per-user profiles Multi-tenant Variable Linear with users Batch parallel High-throughput High Medium
Choose the pattern that matches your workload and resource constraints.