Penelope - Browser Navigator API

Overview

Penelope exposes a persistent browser context through a REST API. The system manages a pool of browser pages/tabs that can be controlled programmatically for web automation, scraping, and testing.

Architecture:
  • System: Lifecycle management for browser and server
  • Ops: Page management (create, destroy, navigate, screenshot)
  • Primitive: Atomic browser actions (click, type, scroll)
  • Plugins: Complex workflows combining primitives

System - Lifecycle Management

GET /api/v1/system/ping

Test server responsiveness. Returns immediate pong response.

GET /api/v1/system/status

Get browser navigator status: initialization state, configuration, page count.

GET /api/v1/system/status/pages

Detailed page pool status: all page IDs, states, URLs, errors.

POST /api/v1/system/start

Initialize or restart browser navigator. Creates fresh browser context and page pool.

POST /api/v1/system/stop

Stop browser navigator, close all pages. Server remains running.

GET /api/v1/system/kill

Graceful shutdown of entire server and browser.

Ops - Page Management

POST /api/v1/ops/create-page

Create new browser page/tab. Returns page metadata with ID.

Body: { "page_id": "custom_id" } // optional
POST /api/v1/ops/<page_id>/destroy-page

Close and destroy specific page.

POST /api/v1/ops/<page_id>/navigate

Navigate page to URL. Waits for domcontentloaded.

Body: { "url": "https://example.com" }
POST /api/v1/ops/<page_id>/extract-content

Extract HTML content from current page: title, full HTML, URL.

GET /api/v1/ops/<page_id>/screenshot

Capture page screenshot. Returns base64-encoded PNG: viewport + full page.

POST /api/v1/ops/<page_id>/search-object

AI vision-based object detection. Returns bounding box and center coordinates.

Body: { "description": "blue submit button" }

Primitive - Atomic Browser Operations

⚠️ Important: Elements must be focused (via click-element) before using type/paste/press operations.
POST /api/v1/primitive/<page_id>/click-element

Click element by CSS selector. Scrolls element into view if needed.

Body: { "selector": "#submit-btn" }
POST /api/v1/primitive/<page_id>/click-position

Click at specific (x, y) coordinates on page.

Body: { "x": 100, "y": 200 }
POST /api/v1/primitive/<page_id>/scroll-page

Scroll page vertically by pixel distance.

Body: { "distance": 1000 } // optional, default: 1000
POST /api/v1/primitive/<page_id>/type-text

Type text character-by-character at focused element. Slow but human-like.

Body: { "text": "hello world" }
POST /api/v1/primitive/<page_id>/paste-text

Paste text via clipboard at focused element. Fast for long content.

Body: { "text": "long content..." }
POST /api/v1/primitive/<page_id>/press-enter

Press Enter key at focused element.

POST /api/v1/primitive/<page_id>/press-tab

Press Tab to move focus forward or Shift+Tab for backward.

Body: { "with_shift": false } // optional
POST /api/v1/primitive/<page_id>/zoom-page

Zoom page at specified coordinates.

Body: { "x": 100, "y": 200, "zoom_level": 1 }
POST /api/v1/primitive/<page_id>/reset-zoom

Reset page zoom to 100%.

Plugins - High-Level Workflows

GET /api/v1/plugins

List all registered plugins with metadata, parameters, and schemas.

POST /api/v1/<page_id>/plugin/archive-check

Check if URL exists on archive.is.

Body: { "url": "https://example.com", "direct_nav_probability": 0.5 }
POST /api/v1/<page_id>/plugin/archive-save

Save URL to archive.is.

Body: { "url": "https://example.com", "from_check": false }
POST /api/v1/<page_id>/plugin/archive-get

Retrieve archived snapshot from archive.is.

Body: { "url": "https://example.com", "from_check": false }
POST /api/v1/<page_id>/plugin/nytimes-search

Search New York Times archive.

Note: Plugins combine primitives into workflows. Parameters validated against schemas. See /api/v1/plugins for details.

Page ID System

Page IDs identify which browser tab to operate on. Use any string (e.g., "main", "scraper1"). Pages auto-created if non-existent. Maximum pool size configurable at server start.

Response Format

All endpoints return JSON with consistent structure:

// Success { "success": true, "page_id": "main", "data": { ... } } // Error { "success": false, "error": "Error description", "page_id": "main" }

Example Usage

// Start browser await fetch('/api/v1/system/start', { method: 'POST' }); // Create page await fetch('/api/v1/ops/create-page', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ page_id: 'scraper' }) }); // Navigate await fetch('/api/v1/ops/scraper/navigate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com' }) }); // Screenshot const shot = await fetch('/api/v1/ops/scraper/screenshot'); // Click and type workflow await fetch('/api/v1/primitive/scraper/click-element', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ selector: 'input[name="q"]' }) }); await fetch('/api/v1/primitive/scraper/type-text', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'search query' }) }); await fetch('/api/v1/primitive/scraper/press-enter', { method: 'POST' }); // Plugin: Archive check await fetch('/api/v1/scraper/plugin/archive-check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com' }) });