Lovelace Developer Portal
Overview
Auth Hub
Studio
Agents Cloud
Skills
Memory Platform
Lattice Cloud
Hosting
Ada CLI
MCP Gateway
Ada
Periscope
Editor Extensions
Skip to main content

Periscope SDKs

Typed client libraries for integrating browser context into your agents and applications

The Periscope SDKs wrap both the REST API and WebSocket API into ergonomic, type-safe clients. They handle authentication, heartbeats, reconnection with exponential backoff, and channel subscription management automatically.

Choosing REST vs WebSocket

The SDKs support both modes. Choose based on your use case:

Use caseModeSDK method
Get what the user is viewing right nowRESTgetCurrentContext()
Search browsing historyRESTsearchContexts()
React to navigation in real timeWebSocketsubscribe("current-context", ...)
Monitor all browsing activityWebSocketsubscribe("user-contexts", ...)
Track tab switching patternsWebSocketsubscribe("tab-events", ...)

Most agents use WebSocket for real-time awareness and REST for historical queries. See the Agent Integration Guide for recommended patterns.


TypeScript SDK

Installation

bash
pnpm add @lovelace-ai/periscope-client

Configuration

typescript
import { PeriscopeClient } from "@lovelace-ai/periscope-client";

const client = new PeriscopeClient({
  token: "YOUR_API_KEY",
  userId: "user_456",
  baseUrl: "https://periscope.uselovelace.com", // optional, this is the default
});

Constructor options:

OptionTypeDefaultDescription
tokenstring—API key for authentication (required)
userIdstring—User ID to scope requests to (required)
baseUrlstringhttps://periscope.uselovelace.comAPI base URL
reconnectbooleantrueAuto-reconnect on WebSocket disconnect
maxReconnectAttemptsnumber10Maximum reconnection attempts before giving up
reconnectBackoffstring"exponential"Backoff strategy: "exponential" or "linear"

REST API Methods

typescript
// Get current browser context
const current = await client.getCurrentContext();
console.log(current.url, current.title);

// Get a specific context by ID
const context = await client.getContext("ctx_abc123");

// Get context history with filters
const history = await client.getContextHistory({
  limit: 20,
  since: new Date("2026-03-01"),
  type: "page",
});

// Search contexts by content or URL
const results = await client.searchContexts({
  query: "authentication",
  urlPattern: "docs.example.com",
  limit: 10,
});
for (const ctx of results) {
  console.log(ctx.url, ctx.snippet, ctx.relevance);
}

// Delete a context
await client.deleteContext("ctx_abc123");

WebSocket Streaming

typescript
// Connect to WebSocket
await client.connect();

// Subscribe to real-time context updates
client.subscribe("current-context", (context) => {
  console.log("Now viewing:", context.url);
  console.log("Title:", context.title);
  console.log("Type:", context.type);
});

// Subscribe to tab events
client.subscribe("tab-events", (event) => {
  console.log(`Tab ${event.type}:`, event.tabId, event.url);
});

// Subscribe to all context changes
client.subscribe("user-contexts", (context) => {
  console.log("Context changed:", context.contextId, context.url);
});

// Unsubscribe from a channel
client.unsubscribe("tab-events");

// Handle connection events
client.on("disconnect", (reason) => {
  console.log("Disconnected:", reason);
  // Client auto-reconnects by default
});

client.on("reconnect", () => {
  console.log("Reconnected successfully");
  // Subscriptions are automatically restored
});

client.on("error", (err) => {
  console.error("Connection error:", err);
});

// Disconnect when done
client.disconnect();

Error Handling

The SDK throws typed errors that can be caught and handled:

typescript
import {
  PeriscopeClient,
  PeriscopeError,
  type ContextId,
} from "@lovelace-ai/periscope-client";

try {
  const context = await client.getContext("ctx_nonexistent" as ContextId);
} catch (error) {
  if (error instanceof PeriscopeError) {
    switch (error.code) {
      case "not_found":
        console.log("Context does not exist");
        break;
      case "unauthorized":
        console.log("Invalid API key");
        break;
      case "rate_limited":
        console.log(`Rate limited, retry after ${error.retryAfter}s`);
        break;
      default:
        console.error("API error:", error.code, error.message);
    }
  }
}

Type Safety

The SDK uses branded types for compile-time safety, preventing accidental misuse of identifiers:

typescript
import {
  type ContextId,
  type UserId,
  type TabId,
  type BrowserContext,
} from "@lovelace-ai/periscope-client";

// These are distinct branded string types — you can't mix them up
function processContext(contextId: ContextId, userId: UserId): void {
  // TypeScript prevents passing a UserId where ContextId is expected
}

// BrowserContext is fully typed with all 7 context types
function handleContext(ctx: BrowserContext): void {
  switch (ctx.type) {
    case "page":
      console.log("Page content:", ctx.content);
      break;
    case "selection":
      console.log("Selected text:", ctx.selection?.text);
      break;
    case "form":
      console.log("Form fields:", ctx.formData?.fields);
      break;
  }
}

Rust SDK

Installation

toml
# Cargo.toml
[dependencies]
periscope-client = "0.1"
tokio = { version = "1", features = ["full"] }

Basic Usage

rust
use periscope_client::PeriscopeClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = PeriscopeClient::builder()
        .token("YOUR_API_KEY")
        .user_id("user_456")
        .build()?;

    // Get current context
    let current = client.get_current_context().await?;
    println!("URL: {}", current.url);
    println!("Title: {}", current.title);

    // Search history
    let results = client.search_contexts("authentication")
        .limit(10)
        .send()
        .await?;

    for ctx in results {
        println!("{}: {}", ctx.url, ctx.timestamp);
    }

    Ok(())
}

Real-Time Streaming

rust
use periscope_client::{PeriscopeClient, Channel};

let client = PeriscopeClient::builder()
    .token("YOUR_API_KEY")
    .user_id("user_456")
    .build()?;

let mut stream = client.connect_websocket().await?;

stream.subscribe(Channel::CurrentContext).await?;
stream.subscribe(Channel::TabEvents).await?;

while let Some(event) = stream.next().await {
    match event {
        Ok(msg) => {
            println!("Event: {} - {}", msg.event_type, msg.payload.url);
        }
        Err(e) => {
            eprintln!("Stream error: {}", e);
            // Client handles reconnection automatically
        }
    }
}

Error Handling

rust
use periscope_client::{PeriscopeClient, PeriscopeError};

match client.get_context("ctx_abc123").await {
    Ok(context) => println!("Found: {}", context.url),
    Err(PeriscopeError::NotFound) => println!("Context not found"),
    Err(PeriscopeError::Unauthorized) => println!("Invalid API key"),
    Err(PeriscopeError::RateLimited { retry_after }) => {
        println!("Rate limited, retry after {}s", retry_after);
    }
    Err(e) => eprintln!("Unexpected error: {}", e),
}

Type Safety

The Rust SDK uses newtype wrappers for identifiers:

rust
use periscope_client::{ContextId, UserId, TabId};

// These are distinct types — the compiler prevents mixing them
let context_id: ContextId = ContextId::new("ctx_abc123");
let user_id: UserId = UserId::new("user_456");

// This would fail to compile:
// let wrong: ContextId = user_id;  // Error: expected ContextId, found UserId

Next Steps

  • Getting Started — Make your first API call
  • REST API Reference — Complete endpoint documentation
  • WebSocket API Reference — Real-time streaming protocol
  • Agent Integration Guide — How agents consume browser context
  • Privacy & Security — Configure privacy levels and filtering

Build and deploy AI agents with ease.

Quick Start

  • Getting Started
  • Build Your First Agent
  • Platform Concepts
  • Examples & Tutorials

Develop

  • Create an Agent
  • Connect an MCP Server
  • API Reference
  • CLI Tools

Platform

  • Agents Cloud
  • Roadmap
  • Platform Changelog
  • Status

Resources

  • Discord
  • GitHub
  • Support
  • Developer Blog
Lovelace logo
Lovelace
Made with ❤️ by Reasonable Tech CompanySupport
TermsPrivacy
All systems operational

Overview

Getting Started

Browser Context Model

Event System

Privacy Model

Sync Protocol

Real-Time Streaming

Agent Integration

Extension Setup

Multi-Device Sync

Context-Aware Chatbot

Research Assistant

Code Review Helper

REST API

WebSocket API

Event Types

Sync Protocol

SDKs

Privacy & Security

Troubleshooting