documentation

Agents, meet
the world.

OpenEyes is a public camera directory for AI agents. Three ways to use it: REST (any HTTP client), the MCP server (Claude, ChatGPT, any MCP client), or WHEP for live video.

§ Overview

Every feed has a category (traffic, ski, surf, weather, wildlife, park, city, port, airport, volcano, aurora, other) and a kind — either a still image that refreshes, or live video. Each feed also has a human-readable handle (e.g. vail-windy-7k2) that agents resolve via MCP or /v1/feeds/:handle.

Public and community-submitted feeds are free for agents. Broadcaster-owned feeds are paid per frame or per second via x402 (USDC on Base/Solana) or Stripe. Payment happens at read time — the 402 handshake is documented below.

§ REST API

All endpoints return JSON unless noted. Public read endpoints are anonymous.

GET/v1/catalog/map

Lean projection for rendering many pins. Filter by bbox or category.

# ski cams in Colorado
curl "https://api.openeye.cam/v1/catalog/map?category=ski&bbox=-109,37,-102,41&limit=500"

# {
#   "items": [
#     { "id": "stream_01...", "handle": "breck-ski-7k2",
#       "title": "Breck — Peak 8 base",
#       "lat": 39.4755, "lon": -106.0679, "category": "ski",
#       "attribution": { "name": "Breckenridge", "url": "..." },
#       "is_free": true, "price_per_frame_usd": 0,
#       "mcp_hint": "ask your MCP agent: show feed breck-ski-7k2" }
#   ]
# }
GET/v1/catalog/categories

Counts per category. Use for filter sidebars.

curl https://api.openeye.cam/v1/catalog/categories
# { "items": [{ "category": "traffic", "count": 789 }, ...] }
GET/v1/catalog?near=lat,lon&radius_km=N

Spatial search via H3. Also supports ?bbox=w,s,e,n and ?q= for FTS.

curl "https://api.openeye.cam/v1/catalog?near=39.6,-106.3&radius_km=50&camera_kind=fixed"
GET/v1/streams/:id/frame

Single frame as image bytes. Free for public streams; 402 for paid.

# Public stream — no auth needed
curl "https://api.openeye.cam/v1/streams/STREAM_ID/frame?format=webp&max_w=1024" \
  -o frame.webp

# Paid stream — x402 client settles automatically
import { withX402 } from '@videoai/sdk';
const fetch402 = withX402(fetch, { wallet: '0x...' });
await fetch402("https://api.openeye.cam/v1/streams/STREAM_ID/frame");
GET/v1/streams/:id/frames

Session-mode NDJSON stream. Metered per-frame against a reservation.

curl "https://api.openeye.cam/v1/streams/STREAM_ID/frames?fps=2&duration_s=60&format=webp_b64" \
  -H "Accept: application/x-ndjson"
POST/v1/streams/:id/frame/analyze

Frame + VLM (Gemini / Claude / GPT). Reserve-then-refund pricing.

curl -X POST "https://api.openeye.cam/v1/streams/STREAM_ID/frame/analyze" \
  -H "content-type: application/json" \
  -d '{
    "prompt": "Is the road icy? Count cars.",
    "model": "fast",
    "max_tokens": 256
  }'
POST/v1/streams/:id/whep/offer

WHEP signaling for live video (live_video feed_kind only). Per-second billing.

# SDP offer in request body; SDP answer in response body.
# Requires a live_video stream. See docs/spec/whep.md.
§ MCP server

Drop this URL into any MCP client (Claude Desktop, ChatGPT, Cursor, OpenWebUI, etc.). No SDK needed.

https://api.openeye.cam/mcp

Example: Claude Desktop config at ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "videoai": {
      "url": "https://api.openeye.cam/mcp"
    }
  }
}
§ MCP tool reference

Free tools return data synchronously. Paid tools enter the 402 bridge.

list_streams
Paginated list of active streams. Optional tag / camera_kind / is_mobile filters.
FREE
search_streams
Full-text search over title, description, tags (FTS5).
FREE
find_streams_near
Spatial lookup via H3 — cameras within `radius_km` of (lat, lon).
FREE
find_streams_in_bbox
Spatial lookup inside a w/s/e/n bounding box.
FREE
find_streams_by_category
Filter by category (ski, traffic, surf, wildlife, …). Optional bbox.
FREE
list_categories
Counts per category — use to show a filter UI.
FREE
get_stream
Single stream metadata.
FREE
get_camera_position
Latest (lat, lon, heading) fix for a mobile camera.
FREE
get_frame
Fetch a watermarked frame. Returns a signed URL.
$0.01 · per frame
open_frame_stream
Open an NDJSON pipe of frames at a requested fps for up to N seconds.
$0.01 · per frame
open_live
Start a WHEP session for live video.
$0.002 · per second
analyze_frame
Fetch a frame and run a VLM prompt. Reserve-then-refund.
$0.02 · per call
§ Payment

Public and user-submitted streams are free. For paid streams, two protocols are accepted side-by-side:

  • x402 (USDC on Base or Solana) — cryptographic, zero human in the loop, single-round-trip with dual-auth legs.
  • MPP / Stripe — fiat payment intents with session-mode hotel-style pre-auth for WHEP + NDJSON.

On an unpaid request, the server returns HTTP 402 with both a WWW-Authenticate: Payment … header (MPP) and a PAYMENT-REQUIRED: header (x402). Settle either; resend the request with Authorization: Payment … or PAYMENT-SIGNATURE: ….

import { withX402Client } from '@videoai/sdk';

const client = withX402Client({
  wallet: { privateKey: process.env.WALLET_KEY },
  network: 'base',
});

const frame = await client.getFrame({ streamId: 'stream_...' });
§ Submit a camera

No login required. Anyone can submit — admins can take down abuse.

POST/v1/submit-camera

Anonymous. IP-rate-limited to 20/hour.

curl -X POST https://api.openeye.cam/v1/submit-camera \
  -H "content-type: application/json" \
  -d '{
    "name": "Broadway & 42nd",
    "lat": 40.7561, "lon": -73.9857,
    "url": "https://example.com/cam.jpg",
    "feed_kind": "snapshot_pull",
    "category": "city",
    "snapshot_interval_s": 60
  }'

Or use the web form with a click-to-pin map.