Skip to main content

Periscope REST API

Complete endpoint reference for browser context sync, retrieval, search, and management

Base URL: https://periscope.uselovelace.com/api/v1

The REST API is the primary interface for syncing browser context from the extension and querying context history from agents and applications. For real-time streaming, see the WebSocket API. For a higher-level client, see the TypeScript and Rust SDKs.

Authentication

All requests require a Bearer token in the Authorization header:

Authorization: Bearer YOUR_API_KEY

API keys are generated through the Lovelace Developer Portal. Keys can be scoped to specific Periscope operations (read, write, delete) for least-privilege access.

OpenAPI Specification

The auto-generated OpenAPI specification is available at:

https://periscope.uselovelace.com/openapi.json

Rate Limiting

All endpoints enforce per-user rate limits. Every response includes rate limit headers:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 115
X-RateLimit-Reset: 1709380800
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

When you receive a 429 response, wait until X-RateLimit-Reset before retrying. See Privacy & Security for rate limit thresholds per endpoint.


Endpoints

POST /context/sync

Sync a browser context from the extension to the Periscope service. This is the primary ingestion endpoint — the extension calls it whenever the user navigates to a new page or triggers a context capture.

The request body is a SyncMessage containing the context payload. The service applies privacy filtering before storing the context.

Request:

json
{
  "id": "msg_001",
  "type": "create",
  "timestamp": "2026-03-02T12:00:00Z",
  "userId": "user_456",
  "extensionId": "ext_chrome_abc",
  "payload": {
    "type": "context",
    "operation": "create",
    "context": {
      "id": "ctx_new123",
      "type": "page",
      "tabId": "tab_789",
      "userId": "user_456",
      "capturedAt": "2026-03-02T12:00:00Z",
      "privacyLevel": "public",
      "tabMetadata": {
        "id": "tab_789",
        "url": "https://docs.example.com/guide",
        "title": "Example Guide",
        "isActive": true,
        "isLoading": false,
        "windowId": 1,
        "lastUpdated": "2026-03-02T12:00:00Z"
      }
    }
  }
}

Request fields:

FieldTypeDescription
idstringUnique message ID for idempotency and correlation
typestringOperation type: create, update, delete, heartbeat, batch, config, error
timestampstringISO 8601 timestamp of when the message was created
userIdUserIdAuthenticated user ID (branded type)
extensionIdstringIdentifier for the browser extension instance
payloadSyncPayloadOne of 4 payload variants — see Sync Protocol

Response (201):

json
{
  "messageId": "msg_001",
  "status": "success",
  "contextId": "ctx_new123",
  "timestamp": "2026-03-02T12:00:00.123Z"
}

Response (207 — partial success for batch):

json
{
  "messageId": "batch_001",
  "status": "partial",
  "results": [
    { "contextId": "ctx_1", "status": "success" },
    {
      "contextId": "ctx_2",
      "status": "error",
      "error": { "code": "validation_error", "message": "Missing tabMetadata" }
    }
  ]
}

POST /context/batch-sync

Sync multiple browser contexts in a single request. Useful when the extension has accumulated multiple context changes while offline or to reduce API calls.

Request:

json
{
  "id": "batch_001",
  "type": "batch",
  "timestamp": "2026-03-02T12:00:00Z",
  "userId": "user_456",
  "extensionId": "ext_chrome_abc",
  "messages": [
    {
      "id": "msg_001",
      "type": "create",
      "timestamp": "2026-03-02T11:58:00Z",
      "payload": {
        "type": "context",
        "operation": "create",
        "context": { "...": "..." }
      }
    },
    {
      "id": "msg_002",
      "type": "create",
      "timestamp": "2026-03-02T11:59:00Z",
      "payload": {
        "type": "context",
        "operation": "create",
        "context": { "...": "..." }
      }
    }
  ]
}
FieldTypeDescription
messagesSyncMessage[]Array of individual sync messages (max 100 per batch)

Response (207):

Each item in results corresponds to the message at the same index. The overall status code is 207 (Multi-Status) — check individual results to determine which items succeeded or failed.

json
{
  "messageId": "batch_001",
  "status": "partial",
  "results": [
    { "contextId": "ctx_abc123", "status": "success" },
    { "contextId": "ctx_def456", "status": "success" }
  ],
  "synced": 2,
  "failed": 0
}

GET /context/current

Get the most recent browser context for the authenticated user. This is the endpoint agents typically poll to understand what the user is currently looking at.

For real-time updates without polling, subscribe to the current-context WebSocket channel instead.

Response (200):

json
{
  "contextId": "ctx_abc123",
  "userId": "user_456",
  "type": "page",
  "url": "https://docs.example.com/guide",
  "title": "Example Guide",
  "content": "Full page content extracted from the browser...",
  "tabId": "tab_123",
  "timestamp": "2026-03-02T12:00:00Z",
  "privacyLevel": "public",
  "tabMetadata": {
    "id": "tab_123",
    "url": "https://docs.example.com/guide",
    "title": "Example Guide",
    "isActive": true,
    "isLoading": false,
    "windowId": 1,
    "lastUpdated": "2026-03-02T12:00:00Z"
  }
}

Response fields:

FieldTypeDescription
contextIdContextIdUnique identifier for this context snapshot (branded type)
userIdUserIdThe authenticated user who generated this context
typeContextTypeOne of: page, selection, element, form, media, navigation, custom
urlstringCurrent page URL from the active browser tab
titlestringPage title from the document's <title> tag
contentstringExtracted page content (text, HTML, or markdown depending on context type)
tabIdTabIdBrowser tab identifier (branded type)
timestampstringISO 8601 timestamp of when the context was captured
privacyLevelPrivacyLevelOne of public, private, restricted, filtered — see Privacy Model
tabMetadataTabMetadataTab state at time of capture (URL, title, active status, window)

Response (404): Returned when no context has been synced yet.


GET /context/history

Get browsing context history for the authenticated user. Returns contexts in reverse chronological order (newest first).

Query Parameters:

ParameterTypeDefaultDescription
limitnumber50Maximum number of results (1–200)
offsetnumber0Pagination offset
sincestringISO 8601 timestamp for start of range (inclusive)
untilstringISO 8601 timestamp for end of range (exclusive)
typestringFilter by context type: page, selection, element, form, media, navigation, custom
privacyLevelstringFilter by privacy level: public, private, restricted, filtered

Response (200):

json
{
  "contexts": [
    {
      "contextId": "ctx_abc123",
      "type": "page",
      "url": "https://docs.example.com/guide",
      "title": "Example Guide",
      "timestamp": "2026-03-02T12:00:00Z",
      "privacyLevel": "public"
    }
  ],
  "total": 142,
  "limit": 50,
  "offset": 0
}

History responses omit content to keep payloads small. Use GET /context/{contextId} to fetch full content for a specific context.


GET /context/search

Search browsing context by content, URL, or metadata. Returns results ranked by relevance.

Query Parameters:

ParameterTypeDefaultDescription
querystringFull-text search query (required unless urlPattern is provided)
urlPatternstringURL substring or pattern match
typestringFilter by context type
privacyLevelstringFilter by privacy level
startTimestringISO 8601 timestamp for start of range
endTimestringISO 8601 timestamp for end of range
limitnumber20Maximum number of results (1–1000)
offsetnumber0Pagination offset

Response (200):

json
{
  "results": [
    {
      "contextId": "ctx_abc123",
      "type": "page",
      "url": "https://docs.example.com/auth",
      "title": "Authentication Guide",
      "snippet": "...configure OAuth 2.1 authentication...",
      "timestamp": "2026-03-02T12:00:00Z",
      "relevance": 0.95
    }
  ],
  "total": 5,
  "query": "authentication",
  "limit": 20,
  "offset": 0
}
FieldDescription
snippetText excerpt surrounding the search match, with context for relevance
relevanceScore from 0.0 to 1.0 indicating how well the result matches the query

GET /context/{contextId}

Get a specific context by ID. Returns the full context including content.

Path Parameters:

ParameterTypeDescription
contextIdContextIdThe context identifier (e.g., ctx_abc123)

Response (200):

json
{
  "contextId": "ctx_abc123",
  "userId": "user_456",
  "type": "page",
  "url": "https://docs.example.com/guide",
  "title": "Example Guide",
  "content": "Full page content...",
  "tabId": "tab_123",
  "timestamp": "2026-03-02T12:00:00Z",
  "privacyLevel": "public",
  "tabMetadata": {
    "id": "tab_123",
    "url": "https://docs.example.com/guide",
    "title": "Example Guide",
    "isActive": true,
    "isLoading": false,
    "windowId": 1,
    "lastUpdated": "2026-03-02T12:00:00Z"
  }
}

Response (404):

json
{
  "error": "not_found",
  "message": "Context not found"
}

DELETE /context/{contextId}

Delete a specific context. Users can only delete their own contexts. Deletion is permanent and cannot be undone.

Path Parameters:

ParameterTypeDescription
contextIdContextIdThe context identifier to delete

Response (204): No content — the context was successfully deleted.

Response (403): Returned when attempting to delete another user's context.

Response (404): Returned when the context does not exist.


Error Responses

All error responses follow a consistent format:

json
{
  "error": "error_code",
  "message": "Human-readable description of what went wrong"
}
StatusError CodeDescriptionResolution
400bad_requestInvalid request body or query parametersCheck request format against the schema above
401unauthorizedMissing or invalid authentication tokenVerify your API key is valid and included in the Authorization header
403forbiddenInsufficient permissions for this operationCheck that your API key has the required scope
404not_foundResource does not existVerify the context ID or check that the extension has synced
207partial_successSome items in a batch succeeded, others failedCheck individual results entries for error details
429rate_limitedToo many requests in the current windowWait until X-RateLimit-Reset and retry
500internal_errorUnexpected server errorRetry with exponential backoff; contact support if persistent

REST vs WebSocket

Use REST when you need to:

  • Fetch the current context on demand (polling)
  • Search or query context history
  • Sync context from custom integrations
  • Delete or manage stored contexts

Use WebSocket when you need to:

  • React to context changes in real time
  • Build agents that continuously monitor browsing activity
  • Minimize latency between user action and agent response

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

Next Steps