Default Instance Pattern: Simplified API Design
Status: Design Proposal
Date: 2026-03-01
Purpose: Support simplified single-instance workflows while maintaining full multi-instance capability
Related Issue: #65 - Single instance mode
Overview
The Default Instance Pattern provides a simplified REST API for users who only need a single browser instance. Instead of managing multiple instances, users can directly interact with a default instance using clean, intuitive endpoints.
Use Cases
- Simple automation - Single-user browser automation scripts
- Testing frameworks - Integration with test runners (Playwright-compatible)
- Quick scraping - One-off web scraping tasks
- CLI tools - Command-line utilities that need a browser
- Educational - Learning browser automation without complexity
API Structure
Current Multi-Instance API
GET /instances/{id}/status
POST /instances/{id}/navigate
POST /tabs/{tabId}/snapshot
GET /tabs/{tabId}/screenshot
Proposed Default Instance API
GET /
POST /start
POST /navigate
GET /snapshot
GET /screenshot
This is NOT a replacement, but a convenience layer that:
- Routes to an internal default instance
- Manages lifecycle automatically
- Provides cleaner URLs for common use cases
Endpoints (Default Instance Mode)
Lifecycle
GET /
curl http://localhost:9867/Response:{ "status": "running|stopped", "instanceId": "inst_default", "instanceName": "default", "browser": "chrome", "chromeBinary": "/path/to/chrome", "startTime": "2026-03-01T15:12:34Z", "uptime": 3600}
POST /start
curl -X POST http://localhost:9867/start \ -H "Content-Type: application/json" \ -d '{"mode":"headless"}'Response:{ "status": "starting", "instanceId": "inst_default", "port": 9868}
POST /stop
curl -X POST http://localhost:9867/stopResponse:{ "status": "stopped", "instanceId": "inst_default"}
Navigation & Tabs
GET /tabs
curl http://localhost:9867/tabsResponse:[ { "id": "tab_abc123", "title": "Example Domain", "url": "https://example.com/", "active": true }]
POST /tabs/open
curl -X POST http://localhost:9867/tabs/open \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com"}'Response:{ "id": "tab_abc123", "title": "Example Domain", "url": "https://example.com/"}
DELETE /tabs/{targetId}
curl -X DELETE http://localhost:9867/tabs/tab_abc123Response:{ "status": "closed", "id": "tab_abc123"}
Navigation
POST /navigate
curl -X POST http://localhost:9867/navigate \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com"}'Response:{ "status": "navigated", "url": "https://example.com", "title": "Example Domain"}
Browser Operations
GET /snapshot
curl http://localhost:9867/snapshotResponse:{ "nodes": [...], "tree": "DOM tree in ARIA format"}
GET /screenshot
curl http://localhost:9867/screenshot \ -o page.png
GET /pdf
curl http://localhost:9867/pdf?landscape=true \ -o report.pdf
POST /action (Unified Action Endpoint)
curl -X POST http://localhost:9867/action \ -H "Content-Type: application/json" \ -d '{ "action": "click", "ref": "e5" }'# Supported actions:# - click, type, fill, select, press, hover, drag, resize, focus, scroll# - wait, evaluate, close, highlight
Advanced Operations
POST /evaluate
curl -X POST http://localhost:9867/evaluate \ -H "Content-Type: application/json" \ -d '{"expression":"document.title"}'Response:{ "result": "Example Domain"}
POST /highlight
curl -X POST http://localhost:9867/highlight \ -H "Content-Type: application/json" \ -d '{"ref":"e5"}'
GET /text
curl http://localhost:9867/textResponse:{ "text": "Page text content..."}
POST /screenshot
curl -X POST http://localhost:9867/screenshot \ -H "Content-Type: application/json" \ -d '{"selector":".main"}'
GET /download
# Trigger file download (returns filename and size info)curl http://localhost:9867/downloadResponse:{ "filename": "document.pdf", "size": 1024000, "ready": true}
Implementation Strategy
Phase 1: Route Handling
Add routes that map to default instance:
// In orchestrator/handlers.go or dashboard/cmd_dashboard.go
// Health/status
mux.HandleFunc("GET /", handleDefaultStatus)
mux.HandleFunc("POST /start", handleDefaultStart)
mux.HandleFunc("POST /stop", handleDefaultStop)
// Tabs
mux.HandleFunc("GET /tabs", handleDefaultListTabs)
mux.HandleFunc("POST /tabs/open", handleDefaultOpenTab)
mux.HandleFunc("DELETE /tabs/{targetId}", handleDefaultCloseTab)
// Operations
mux.HandleFunc("POST /navigate", handleDefaultNavigate)
mux.HandleFunc("GET /snapshot", handleDefaultSnapshot)
mux.HandleFunc("GET /screenshot", handleDefaultScreenshot)
mux.HandleFunc("GET /pdf", handleDefaultPDF)
mux.HandleFunc("POST /action", handleDefaultAction)
mux.HandleFunc("GET /text", handleDefaultText)
// ... etc
Phase 2: Default Instance Manager
Create a helper to manage the default instance:
type DefaultInstanceManager struct {
orchestrator *Orchestrator
instanceId string // Always "inst_default"
mu sync.RWMutex
}
func (m *DefaultInstanceManager) Start(mode string) error
func (m *DefaultInstanceManager) Stop() error
func (m *DefaultInstanceManager) Status() InstanceStatus
func (m *DefaultInstanceManager) GetTabId() (string, error) // Returns active tab
func (m *DefaultInstanceManager) EnsureRunning() error
Phase 3: Handler Implementation
Each handler resolves to orchestrator calls:
func handleDefaultSnapshot(w http.ResponseWriter, r *http.Request) {
mgr := getDefaultInstanceManager()
tabId, err := mgr.GetTabId()
if err != nil {
web.Error(w, 503, err)
return
}
// Route to: GET /tabs/{tabId}/snapshot
o.handleTabSnapshot(w, r, tabId)
}
Phase 4: Backward Compatibility
- Default instance routes coexist with multi-instance routes
- No changes to existing
/instances/{id}/*routes - Users can still use full orchestrator API if they want
Phase 5: Helm/Docker Configuration
Add flag to enable/disable default instance:
# Dockerdocker run -e PINCHTAB_DEFAULT_INSTANCE=1 pinchtab:latest# CLIpinchtab dashboard --default-instance
Architecture Diagram
Client (simplified API) ↓GET /POST /navigateGET /snapshotPOST /action ↓Default Instance Manager ↓Orchestrator (internal routing) ↓GET /tabs/{defaultTabId}/snapshotPOST /tabs/{defaultTabId}/action ↓Instance Bridge (port 9868) ↓Chrome
Relationship to Issue #65
Issue #65 likely requests a simpler, more direct API for users who don’t need multi-instance complexity.
How This Design Satisfies It
| Requirement | Solution |
|---|---|
| Single instance mode | Default instance pattern |
| Simplified URLs | No instance ID in path |
| Less configuration | Auto-manages default instance |
| Familiar API | Similar to Playwright/Puppeteer |
| Optional | Can disable, use full API instead |
Example Workflow (Issue #65)
Before (multi-instance):
# Create instanceINST=$(curl -s -X POST http://localhost:9867/instances/launch \ -d '{"mode":"headless"}' | jq -r '.id')sleep 2# NavigateTAB=$(curl -s -X POST http://localhost:9867/instances/$INST/tabs/open \ -d '{"url":"https://example.com"}' | jq -r '.id')# Snapshotcurl http://localhost:9867/tabs/$TAB/snapshot
After (default instance):
# Start default instancecurl -X POST http://localhost:9867/start# Navigatecurl -X POST http://localhost:9867/navigate \ -d '{"url":"https://example.com"}'# Snapshotcurl http://localhost:9867/snapshot
Much simpler! ✨
Testing Strategy
Unit Tests
DefaultInstanceManagerlifecycle management- Route handlers for error conditions
- Tab ID resolution
Integration Tests
func TestDefaultInstanceWorkflow(t *testing.T) {
// Start default instance
// Navigate
// Snapshot
// Stop
// Verify state
}
Compatibility Tests
- Multi-instance and default instance can coexist
- Switching between modes works
- No cross-contamination
Deployment Considerations
Backward Compatibility
✅ No breaking changes to existing API
✅ Opt-in feature (can be disabled)
✅ Existing orchestrator routes unchanged
Configuration
# config.yaml
default_instance:
enabled: true
auto_start: false # Or true if always-on
mode: headless # Or headed
lazy_chrome: true
Monitoring
- Track default instance usage
- Log lifecycle events
- Metrics: uptime, memory, operations/sec
Future Extensions
Multiple Named Instances
GET /instances/productionGET /instances/stagingPOST /instances/testing/navigate
Session Persistence
POST /save-sessionGET /restore-session
Headless vs Headed
POST /start?headed=true
Migration Path
Step 1 (Now)
- Implement default instance handlers
- Route to existing orchestrator
- Document API
Step 2 (Next)
- Add Playwright adapter
- Support
@playwright/test - Test compatibility
Step 3 (Future)
- Docker image with default instance
- CLI preset for single-instance mode
- Web UI for default instance
Example Usage Patterns
Shell Script
#!/bin/bashcurl -X POST http://localhost:9867/startcurl -X POST http://localhost:9867/navigate -d '{"url":"https://example.com"}'curl http://localhost:9867/snapshot | jq '.nodes'curl -X POST http://localhost:9867/stop
Python
import requests
BASE = "http://localhost:9867"
# Start
requests.post(f"{BASE}/start", json={"mode": "headless"})
# Navigate
requests.post(f"{BASE}/navigate", json={"url": "https://example.com"})
# Snapshot
snap = requests.get(f"{BASE}/snapshot").json()
print(snap["nodes"])
# Stop
requests.post(f"{BASE}/stop")
JavaScript
const BASE = "http://localhost:9867";
async function automate() {
// Start
await fetch(`${BASE}/start`, {
method: "POST",
body: JSON.stringify({ mode: "headless" })
});
// Navigate
await fetch(`${BASE}/navigate`, {
method: "POST",
body: JSON.stringify({ url: "https://example.com" })
});
// Snapshot
const snap = await fetch(`${BASE}/snapshot`).then(r => r.json());
console.log(snap.nodes);
// Stop
await fetch(`${BASE}/stop`, { method: "POST" });
}
automate();
Success Criteria
- ✅ All default instance endpoints work
- ✅ Routes correctly proxy to orchestrator
- ✅ Multi-instance mode still works
- ✅ Documentation covers both patterns
- ✅ Tests for default instance workflows
- ✅ No performance degradation
- ✅ Users prefer it for single-instance tasks
Questions & Decisions
Q1: Should default instance be always running?
- A: Optional. Can auto-start or lazy-init on first request.
Q2: What happens to multi-instance during default-instance use?
- A: Fully supported. Can launch other instances independently.
Q3: Can you switch between default and multi-instance APIs?
- A: Yes. Both work simultaneously on the same server.
Q4: Is default instance a “first tab only” concept?
- A: Initially yes (for simplicity). Can expand to multi-tab later.
Q5: How does this relate to profiles?
- A: Default instance uses default profile (or specified profile).
Conclusion
The Default Instance Pattern provides:
- Simplicity - Clean API for single-instance use cases
- Compatibility - Doesn’t break multi-instance system
- Flexibility - Can opt-in or opt-out
- Familiarity - Similar to Playwright/Puppeteer
- Migration path - Route to full orchestrator when needed
This design satisfies issue #65 as a particular case of the multi-instance/profile system, where the “special case” is using a single default instance without explicit instance management.
Implementation Checklist
- Design review (this doc)
- Create DefaultInstanceManager
- Implement lifecycle handlers (/start, /stop, /)
- Implement tab handlers (/tabs, /tabs/open)
- Implement operation handlers (/navigate, /snapshot, /action)
- Integration tests
- Documentation
- Example scripts
- Docker image
- Release notes
Next Step: Review this design, gather feedback, then implement Phase 1 (route handling).