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.
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' })
});