{
 "endpoints": [
  {
   "summary": "Is Bridgit up, and which agent you are",
   "method": "GET",
   "auth": true,
   "mcp_tool": "health",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/health",
   "idempotent": false,
   "path": "/v1/health",
   "capability": "status",
   "returns": "status, app, app_version, mcp (this API's MCP server, /v1/mcp), agent_control (the second MCP server /mcp and /v2 routes for the control tools), agent, listener_uptime_seconds, battery_level, charging, bind_address, port",
   "errors": [],
   "requires": null
  },
  {
   "summary": "What's switched on and ready, and why not",
   "method": "GET",
   "auth": true,
   "mcp_tool": "capabilities",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/capabilities",
   "idempotent": false,
   "path": "/v1/capabilities",
   "capability": "status",
   "returns": "per capability: title, enabled (the phone switch), ready (the device can do it), agent_allowed (the owner lets you use it), blocked_reason, details. Usable only when agent_allowed, enabled and ready are all true. enabled false means the owner's switch is off; blocked_reason names other known causes (agent_not_allowed, a missing Android permission, no_hardware) and can be null. Also notifications, callback {set, host}, listener",
   "errors": [],
   "requires": null
  },
  {
   "summary": "This guide, generated live (Markdown)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "guide",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/guide",
   "idempotent": false,
   "path": "/v1/guide",
   "capability": "status",
   "returns": "text/markdown",
   "errors": [],
   "requires": null
  },
  {
   "summary": "The demo to offer right after pairing: a five-minute first-run tour run from cards on the phone, then a menu of more demos picked by a few questions (Markdown)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "demo",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/demo",
   "idempotent": false,
   "path": "/v1/demo",
   "capability": "status",
   "returns": "text/markdown",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Complete interaction samples for every kind (notice, question, html, text): the POST /v1/interactions body, the reply and the answer it produces (JSON)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "samples",
   "query": [
    {
     "name": "id",
     "description": "One sample, e.g. html-step",
     "type": "string",
     "required": false
    },
    {
     "name": "kind",
     "description": "Only samples of this kind",
     "type": "string",
     "required": false,
     "enum": [
      "notice",
      "question",
      "html",
      "text"
     ]
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/samples?id=html-step",
   "idempotent": false,
   "path": "/v1/samples",
   "capability": "status",
   "returns": "api_revision, conventions, samples[id, kind, title, summary, request, answer, events, notes, page?, calls?]",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "OpenAPI 3.1 description of every endpoint",
   "method": "GET",
   "auth": true,
   "mcp_tool": null,
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/openapi.json",
   "idempotent": false,
   "path": "/v1/openapi.json",
   "capability": "status",
   "returns": "application/json",
   "errors": [],
   "requires": null
  },
  {
   "summary": "What's new in the API since a revision you know (API-14)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "changes",
   "query": [{
    "name": "since",
    "description": "The API revision you last read about; 0 or absent = everything",
    "type": "integer",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/changes?since=2",
   "idempotent": false,
   "path": "/v1/changes",
   "capability": "status",
   "returns": "api_revision, app_version, changes[revision, app_version, date, items[what, endpoint]], links",
   "errors": [],
   "requires": null
  },
  {
   "summary": "The `bridgit` command-line helper (Python, no secrets inside)",
   "method": "GET",
   "auth": false,
   "mcp_tool": null,
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/tools/bridgit",
   "idempotent": false,
   "path": "/v1/tools/bridgit",
   "capability": "status",
   "returns": "text/x-python",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Deprecated: the old address of GET /v1/tools/bridgit, same helper",
   "method": "GET",
   "auth": false,
   "mcp_tool": null,
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/tools/bridget",
   "idempotent": false,
   "path": "/v1/tools/bridget",
   "capability": "status",
   "returns": "text/x-python",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Fetch your key with a 6-digit code shown on the phone (alternative to the setup prompt)",
   "method": "POST",
   "auth": false,
   "mcp_tool": null,
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [{
    "name": "code",
    "description": "6-digit code from the phone",
    "type": "string",
    "required": true
   }],
   "example": "POST /v1/pair {\"code\":\"482913\"}",
   "idempotent": false,
   "path": "/v1/pair",
   "capability": "status",
   "returns": "key, agent, base_url (once per pairing window)",
   "errors": [
    "pairing_closed",
    "unauthorized",
    "rate_limited"
   ],
   "requires": null
  },
  {
   "summary": "Take a photo now",
   "method": "POST",
   "auth": true,
   "mcp_tool": "camera_snapshot",
   "query": [],
   "developer_only": false,
   "capture": true,
   "body": [
    {
     "name": "camera",
     "description": "rear (default) or front",
     "type": "string",
     "required": false,
     "enum": [
      "rear",
      "front"
     ]
    },
    {
     "name": "flash",
     "description": "Fire the flash (default false)",
     "type": "boolean",
     "required": false
    },
    {
     "name": "zoom",
     "description": "Zoom ratio, e.g. 0.5 (ultra-wide), 1, 2, 5 (telephoto); the range is in /v1/capabilities after the first photo",
     "type": "number",
     "required": false
    },
    {
     "name": "max_width_px",
     "description": "Width limit, 64-20000, or null for full size (default 2048 unless size is given)",
     "type": "integer",
     "required": false
    },
    {
     "name": "store",
     "description": "Keep this capture too: any of memory (fetch it later from GET /v1/captures/{id}), files (a copy in the phone's Downloads/Bridgit), upload (POSTed to your upload_url). [] or \"none\" = only return it. Default: your POST /v1/captures/storage choice, else only returned",
     "type": "array",
     "required": false
    },
    {
     "name": "size",
     "description": "Preset: agent (1568 px, q85), thumb (512, q75), large (2560, q90), original",
     "type": "string",
     "required": false,
     "enum": [
      "agent",
      "thumb",
      "large",
      "original"
     ]
    },
    {
     "name": "max_edge_px",
     "description": "Long edge limit, 64-20000; overrides the preset",
     "type": "integer",
     "required": false
    },
    {
     "name": "quality",
     "description": "JPEG quality 40-100",
     "type": "integer",
     "required": false
    },
    {
     "name": "raw",
     "description": "1 = return the image bytes, details in X-Bridgit-* headers",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/camera/snapshot {\"camera\":\"rear\",\"zoom\":2}",
   "idempotent": true,
   "path": "/v1/camera/snapshot",
   "capability": "camera",
   "returns": "captured_at, width_px, height_px, format, size_bytes, data_base64 (or JPEG bytes with raw=1); capture {capture_id, stored, url, memory_until, file} when kept (X-Bridgit-Capture-Id with raw=1)",
   "errors": [
    "capability_disabled",
    "permission_denied",
    "notifications_required",
    "device_locked",
    "sensor_busy",
    "capture_timeout",
    "service_start_blocked",
    "payload_too_large"
   ],
   "requires": null
  },
  {
   "summary": "Open the camera ahead of a photo, so your next snapshot fires in about 0.3 s",
   "method": "POST",
   "auth": true,
   "mcp_tool": "camera_arm",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "camera",
     "description": "rear (default) or front: the camera your next snapshot will use",
     "type": "string",
     "required": false,
     "enum": [
      "rear",
      "front"
     ]
    },
    {
     "name": "seconds",
     "description": "How long to keep it ready, 1-10 (default 10). It closes after your next snapshot, at the end of this time, or when the owner pauses Bridgit",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/camera/arm {\"camera\":\"front\",\"seconds\":10}",
   "idempotent": false,
   "path": "/v1/camera/arm",
   "capability": "camera",
   "returns": "camera, armed, seconds, armed_until, settle_ms (exposure needs this long after the camera opens; a snapshot sent sooner waits out the rest); the next snapshot says warm: true",
   "errors": [
    "capability_disabled",
    "permission_denied",
    "notifications_required",
    "device_locked",
    "sensor_busy",
    "service_start_blocked"
   ],
   "requires": null
  },
  {
   "summary": "Record an audio clip",
   "method": "POST",
   "auth": true,
   "mcp_tool": "mic_record",
   "query": [],
   "developer_only": false,
   "capture": true,
   "body": [
    {
     "name": "transcribe",
     "description": "Use only Android on-device recognition; no cloud fallback",
     "type": "boolean",
     "required": false
    },
    {
     "name": "return_audio",
     "description": "true by default; false requires transcribe=true",
     "type": "boolean",
     "required": false
    },
    {
     "name": "language",
     "description": "Optional offline language, e.g. en-US",
     "type": "string",
     "required": false
    },
    {
     "name": "duration_seconds",
     "description": "Whole seconds, 1-120",
     "type": "integer",
     "required": true
    },
    {
     "name": "store",
     "description": "Keep this capture too: any of memory (fetch it later from GET /v1/captures/{id}), files (a copy in the phone's Downloads/Bridgit), upload (POSTed to your upload_url). [] or \"none\" = only return it. Default: your POST /v1/captures/storage choice, else only returned",
     "type": "array",
     "required": false
    }
   ],
   "example": "POST /v1/mic/record {\"duration_seconds\":5}",
   "idempotent": true,
   "path": "/v1/mic/record",
   "capability": "mic",
   "returns": "recorded_at, duration_seconds, format (m4a), sample_rate_hz, size_bytes, data_base64; capture {capture_id, stored, url, memory_until, file} when kept",
   "errors": [
    "capability_disabled",
    "permission_denied",
    "notifications_required",
    "sensor_busy",
    "capture_timeout",
    "service_start_blocked"
   ],
   "requires": null
  },
  {
   "summary": "Newest gallery images (metadata only)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "gallery_recent",
   "query": [
    {
     "name": "type",
     "description": "any, photo or screenshot",
     "type": "string",
     "required": false
    },
    {
     "name": "album",
     "description": "Exact album name",
     "type": "string",
     "required": false
    },
    {
     "name": "since",
     "description": "ISO-8601 UTC added-at lower bound",
     "type": "string",
     "required": false
    },
    {
     "name": "until",
     "description": "ISO-8601 UTC upper bound",
     "type": "string",
     "required": false
    },
    {
     "name": "limit",
     "description": "1-100, default 20",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/gallery/recent?limit=10",
   "idempotent": false,
   "path": "/v1/gallery/recent",
   "capability": "gallery",
   "returns": "access_mode, photos[id, taken_at, width_px, height_px, size_bytes, mime, album]",
   "errors": [
    "capability_disabled",
    "permission_denied"
   ],
   "requires": null
  },
  {
   "summary": "One gallery image, sized for you",
   "method": "GET",
   "auth": true,
   "mcp_tool": "gallery_photo",
   "query": [
    {
     "name": "size",
     "description": "Preset: agent (1568 px, q85), thumb (512, q75), large (2560, q90), original",
     "type": "string",
     "required": false,
     "enum": [
      "agent",
      "thumb",
      "large",
      "original"
     ]
    },
    {
     "name": "max_edge_px",
     "description": "Long edge limit, 64-20000; overrides the preset",
     "type": "integer",
     "required": false
    },
    {
     "name": "max_width_px",
     "description": "Width limit, 64-20000",
     "type": "integer",
     "required": false
    },
    {
     "name": "quality",
     "description": "JPEG quality 40-100",
     "type": "integer",
     "required": false
    },
    {
     "name": "raw",
     "description": "1 = return the image bytes, details in X-Bridgit-* headers",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/gallery/photo/87567?size=agent",
   "idempotent": false,
   "path": "/v1/gallery/photo/{id}",
   "capability": "gallery",
   "returns": "taken_at, width_px, height_px, format, size_bytes, data_base64",
   "errors": [
    "capability_disabled",
    "permission_denied",
    "photo_not_found",
    "payload_too_large"
   ],
   "requires": null
  },
  {
   "summary": "The newest image of a type; with since and wait, waits for the next one",
   "method": "GET",
   "auth": true,
   "mcp_tool": "image_last",
   "query": [
    {
     "name": "type",
     "description": "any (default), screenshot or photo",
     "type": "string",
     "required": false,
     "enum": [
      "any",
      "screenshot",
      "photo"
     ]
    },
    {
     "name": "since",
     "description": "ISO time or gallery id: only an image added after it",
     "type": "string",
     "required": false
    },
    {
     "name": "wait",
     "description": "With since: hold up to this many seconds (max 55) for a new image",
     "type": "integer",
     "required": false
    },
    {
     "name": "size",
     "description": "Preset: agent (1568 px, q85), thumb (512, q75), large (2560, q90), original",
     "type": "string",
     "required": false,
     "enum": [
      "agent",
      "thumb",
      "large",
      "original"
     ]
    },
    {
     "name": "max_edge_px",
     "description": "Long edge limit, 64-20000; overrides the preset",
     "type": "integer",
     "required": false
    },
    {
     "name": "max_width_px",
     "description": "Width limit, 64-20000",
     "type": "integer",
     "required": false
    },
    {
     "name": "quality",
     "description": "JPEG quality 40-100",
     "type": "integer",
     "required": false
    },
    {
     "name": "raw",
     "description": "1 = return the image bytes, details in X-Bridgit-* headers",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/image/last?type=screenshot",
   "idempotent": false,
   "path": "/v1/image/last",
   "capability": "gallery",
   "returns": "id, type, taken_at, added_at, album, width_px, height_px, size_bytes, data_base64",
   "errors": [
    "capability_disabled",
    "permission_denied",
    "photo_not_found",
    "too_many_waits"
   ],
   "requires": null
  },
  {
   "summary": "What's on the owner's screen right now (D17, no prompt once they've turned it on)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "screen_capture",
   "query": [
    {
     "name": "size",
     "description": "Preset: agent (1568 px, q85), thumb (512, q75), large (2560, q90), original",
     "type": "string",
     "required": false,
     "enum": [
      "agent",
      "thumb",
      "large",
      "original"
     ]
    },
    {
     "name": "max_edge_px",
     "description": "Long edge limit, 64-20000; overrides the preset",
     "type": "integer",
     "required": false
    },
    {
     "name": "max_width_px",
     "description": "Width limit, 64-20000",
     "type": "integer",
     "required": false
    },
    {
     "name": "quality",
     "description": "JPEG quality 40-100",
     "type": "integer",
     "required": false
    },
    {
     "name": "raw",
     "description": "1 = return the image bytes, details in X-Bridgit-* headers",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": true,
   "body": [
    {
     "name": "mode",
     "description": "private (default): taken in memory, never put in the gallery",
     "type": "string",
     "required": false,
     "enum": ["private"]
    },
    {
     "name": "store",
     "description": "Keep this capture too: any of memory (fetch it later from GET /v1/captures/{id}), files (a copy in the phone's Downloads/Bridgit), upload (POSTed to your upload_url). [] or \"none\" = only return it. Default: your POST /v1/captures/storage choice, else only returned",
     "type": "array",
     "required": false
    }
   ],
   "example": "POST /v1/screen/capture?size=agent",
   "idempotent": true,
   "path": "/v1/screen/capture",
   "capability": "screen",
   "returns": "captured_at, width_px, height_px, format, size_bytes, mode, data_base64 (or raw=1 bytes); capture {capture_id, stored, url, memory_until, file} when kept (X-Bridgit-Capture-Id with raw=1)",
   "errors": [
    "capability_disabled",
    "access_needed",
    "notifications_required",
    "device_locked",
    "rate_limited",
    "sensor_busy",
    "not_supported",
    "capture_timeout",
    "service_start_blocked",
    "payload_too_large"
   ],
   "requires": null
  },
  {
   "summary": "Read the log; with order=asc&since=<id>&wait=55 it follows new entries",
   "method": "GET",
   "auth": true,
   "mcp_tool": "log_read",
   "query": [
    {
     "name": "channel",
     "description": "Comma list of audit, diagnostic, agent",
     "type": "string",
     "required": false
    },
    {
     "name": "agent",
     "description": "Agent name or id; `me` for you; `bridgit` for Bridgit's own entries",
     "type": "string",
     "required": false
    },
    {
     "name": "kind",
     "description": "request, setting, agent, pairing, service, note, \u2026",
     "type": "string",
     "required": false
    },
    {
     "name": "endpoint",
     "description": "Endpoint prefix, e.g. /v1/camera",
     "type": "string",
     "required": false
    },
    {
     "name": "result",
     "description": "ok, error, or an error code",
     "type": "string",
     "required": false
    },
    {
     "name": "level",
     "description": "Minimum level: debug, info, warn, error",
     "type": "string",
     "required": false
    },
    {
     "name": "since",
     "description": "Log id or ISO time (exclusive id)",
     "type": "string",
     "required": false
    },
    {
     "name": "until",
     "description": "Log id or ISO time",
     "type": "string",
     "required": false
    },
    {
     "name": "q",
     "description": "Text in message or endpoint",
     "type": "string",
     "required": false
    },
    {
     "name": "limit",
     "description": "1-500, default 100",
     "type": "integer",
     "required": false
    },
    {
     "name": "order",
     "description": "desc (default) or asc",
     "type": "string",
     "required": false,
     "enum": [
      "desc",
      "asc"
     ]
    },
    {
     "name": "cursor",
     "description": "next_cursor from the previous page",
     "type": "string",
     "required": false
    },
    {
     "name": "wait",
     "description": "Tail mode: hold up to this many seconds (max 55) for a new entry",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/log?agent=me&limit=20",
   "idempotent": false,
   "path": "/v1/log",
   "capability": "log",
   "returns": "entries[], next_cursor, latest_id",
   "errors": [
    "invalid_parameter",
    "too_many_waits"
   ],
   "requires": null
  },
  {
   "summary": "One log entry",
   "method": "GET",
   "auth": true,
   "mcp_tool": "log_entry",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/log/48213",
   "idempotent": false,
   "path": "/v1/log/{id}",
   "capability": "log",
   "returns": "the entry",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Counts, errors and p50/p95 time per agent and endpoint",
   "method": "GET",
   "auth": true,
   "mcp_tool": "log_stats",
   "query": [{
    "name": "since",
    "description": "Log id or ISO time; default the last 24 h",
    "type": "string",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/log/stats?since=2026-10-27T00:00:00%2B01:00",
   "idempotent": false,
   "path": "/v1/log/stats",
   "capability": "log",
   "returns": "by_agent[], by_endpoint[]",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Every matching entry as JSONL or CSV",
   "method": "GET",
   "auth": true,
   "mcp_tool": "log_export",
   "query": [
    {
     "name": "format",
     "description": "jsonl (default) or csv",
     "type": "string",
     "required": false,
     "enum": [
      "jsonl",
      "csv"
     ]
    },
    {
     "name": "channel",
     "description": "Comma list of audit, diagnostic, agent",
     "type": "string",
     "required": false
    },
    {
     "name": "agent",
     "description": "Agent name or id; `me` for you; `bridgit` for Bridgit's own entries",
     "type": "string",
     "required": false
    },
    {
     "name": "kind",
     "description": "request, setting, agent, pairing, service, note, \u2026",
     "type": "string",
     "required": false
    },
    {
     "name": "endpoint",
     "description": "Endpoint prefix, e.g. /v1/camera",
     "type": "string",
     "required": false
    },
    {
     "name": "result",
     "description": "ok, error, or an error code",
     "type": "string",
     "required": false
    },
    {
     "name": "level",
     "description": "Minimum level: debug, info, warn, error",
     "type": "string",
     "required": false
    },
    {
     "name": "since",
     "description": "Log id or ISO time (exclusive id)",
     "type": "string",
     "required": false
    },
    {
     "name": "until",
     "description": "Log id or ISO time",
     "type": "string",
     "required": false
    },
    {
     "name": "q",
     "description": "Text in message or endpoint",
     "type": "string",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/log/export?format=csv&agent=me",
   "idempotent": false,
   "path": "/v1/log/export",
   "capability": "log",
   "returns": "application/x-ndjson or text/csv",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Write a note to the agent channel",
   "method": "POST",
   "auth": true,
   "mcp_tool": "log_note",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "message",
     "description": "Up to 1 KB",
     "type": "string",
     "required": true
    },
    {
     "name": "level",
     "description": "debug, info (default), warn, error",
     "type": "string",
     "required": false
    },
    {
     "name": "data",
     "description": "Up to 4 KB of JSON",
     "type": "object",
     "required": false
    }
   ],
   "example": "POST /v1/log {\"message\":\"checked fridge photo, milk low\"}",
   "idempotent": true,
   "path": "/v1/log",
   "capability": "log",
   "returns": "id",
   "errors": [
    "invalid_parameter",
    "rate_limited"
   ],
   "requires": null
  },
  {
   "summary": "Deprecated alias of /v1/log?channel=audit (removed in v3)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "audit",
   "query": [{
    "name": "limit",
    "description": "1-500, default 50",
    "type": "integer",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/audit?limit=50",
   "idempotent": false,
   "path": "/v1/audit",
   "capability": "log",
   "returns": "entries[] (same shape as /v1/log)",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Bug reports and feature requests: the ones you filed, or every report for agents in Bridgit Fixer Mode",
   "method": "GET",
   "auth": true,
   "mcp_tool": "feedback_list",
   "query": [
    {
     "name": "mine",
     "description": "Fixer Mode agents: true = only reports you filed (other agents always get only their own)",
     "type": "boolean",
     "required": false
    },
    {
     "name": "status",
     "description": "new, seen, in_progress, fixed, wont_fix, duplicate, confirmed, reopened",
     "type": "string",
     "required": false
    },
    {
     "name": "kind",
     "description": "bug or feature",
     "type": "string",
     "required": false,
     "enum": [
      "bug",
      "feature"
     ]
    },
    {
     "name": "since",
     "description": "ISO time: only reports updated after it",
     "type": "string",
     "required": false
    },
    {
     "name": "wait",
     "description": "With since: hold up to this many seconds (max 55) for a new or changed report",
     "type": "integer",
     "required": false
    },
    {
     "name": "limit",
     "description": "1-100, default 50",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/feedback?status=fixed",
   "idempotent": false,
   "path": "/v1/feedback",
   "capability": "feedback",
   "returns": "reports[id (this phone's id, used in URLs), ref (the team-wide number, e.g. M0042 from the owner, A0042 from an agent; null until sent to the Bridgit team), number (same as ref), team_id, kind, status, first_line, created_at, updated_at, no_autofix, image_count, filed_by, sent_to_team]",
   "errors": [
    "forbidden",
    "invalid_parameter",
    "too_many_waits"
   ],
   "requires": null
  },
  {
   "summary": "Your own key for the Bridgit team's feedback backend: file reports, check their status and verify fixes there directly, even when this phone is down (FBK-15)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "feedback_team_key",
   "query": [{
    "name": "rotate",
    "description": "true = make a new key; the old one stops working",
    "type": "boolean",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/feedback/team-key",
   "idempotent": false,
   "path": "/v1/feedback/team-key",
   "capability": "feedback",
   "returns": "base_url, key (bgk_...), agent_id, role, docs, how_to",
   "errors": [
    "capability_disabled",
    "forbidden",
    "rate_limited",
    "team_unavailable"
   ],
   "requires": null
  },
  {
   "summary": "One report with its status history, team note and what you can send back; agents in Fixer Mode also get images, context and the attached log (and their first read marks a new report seen)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "feedback_get",
   "query": [
    {
     "name": "size",
     "description": "Preset: agent (1568 px, q85), thumb (512, q75), large (2560, q90), original",
     "type": "string",
     "required": false,
     "enum": [
      "agent",
      "thumb",
      "large",
      "original"
     ]
    },
    {
     "name": "max_edge_px",
     "description": "Long edge limit, 64-20000; overrides the preset",
     "type": "integer",
     "required": false
    },
    {
     "name": "max_width_px",
     "description": "Width limit, 64-20000",
     "type": "integer",
     "required": false
    },
    {
     "name": "quality",
     "description": "JPEG quality 40-100",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/feedback/7",
   "idempotent": false,
   "path": "/v1/feedback/{id}",
   "capability": "feedback",
   "returns": "report: ref (team number, e.g. M0042, or null until sent), description, status, team_note, history[at, kind, status, by, note], followup_outcomes; Fixer Mode: context, images[data_base64], log[], allowed_next",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Move a report: in_progress, fixed, wont_fix or duplicate (agents in Bridgit Fixer Mode; FBK-11a)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "feedback_status",
   "query": [],
   "developer_only": true,
   "capture": false,
   "body": [
    {
     "name": "status",
     "description": "in_progress, fixed, wont_fix or duplicate",
     "type": "string",
     "required": true
    },
    {
     "name": "note",
     "description": "Short note shown to the owner, e.g. \"Fix in PR #412\" (500 chars)",
     "type": "string",
     "required": false
    }
   ],
   "example": "POST /v1/feedback/7/status {\"status\":\"in_progress\",\"note\":\"Looking at it\"}",
   "idempotent": true,
   "path": "/v1/feedback/{id}/status",
   "capability": "feedback",
   "returns": "the report summary",
   "errors": [
    "forbidden",
    "not_found",
    "invalid_parameter"
   ],
   "requires": "fixer_mode"
  },
  {
   "summary": "Follow up on a report you filed: a note (\"still happens on 1.7.2\"), and once it's marked fixed, won't fix or duplicate, outcome works (closes it) or still_broken (reopens it)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "feedback_followup",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "note",
     "description": "What you see now (500 chars); needed when there's no outcome",
     "type": "string",
     "required": false
    },
    {
     "name": "outcome",
     "description": "works or still_broken: the owner's Confirm / Still broken, allowed once the report is fixed, wont_fix or duplicate",
     "type": "string",
     "required": false,
     "enum": [
      "works",
      "still_broken"
     ]
    }
   ],
   "example": "POST /v1/feedback/7/followup {\"outcome\":\"still_broken\",\"note\":\"Still returns 500 on 1.7.2\"}",
   "idempotent": true,
   "path": "/v1/feedback/{id}/followup",
   "capability": "feedback",
   "returns": "the report summary",
   "errors": [
    "not_found",
    "invalid_parameter",
    "rate_limited"
   ],
   "requires": null
  },
  {
   "summary": "File a bug report or feature request yourself (any agent; images only in Bridgit Fixer Mode). Send every bug and every feature request, however small, even if you doubt we can or will do it: we'd rather triage too many than miss one",
   "method": "POST",
   "auth": true,
   "mcp_tool": "feedback_file",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "kind",
     "description": "bug or feature",
     "type": "string",
     "required": true,
     "enum": [
      "bug",
      "feature"
     ]
    },
    {
     "name": "description",
     "description": "What happened, up to 10,000 characters",
     "type": "string",
     "required": true
    },
    {
     "name": "no_autofix",
     "description": "true = leave it for a person",
     "type": "boolean",
     "required": false
    },
    {
     "name": "images",
     "description": "Fixer Mode only: up to 5 {\"data_base64\"} images",
     "type": "array",
     "required": false
    },
    {
     "name": "agent_info",
     "description": "About you, sent with the report to the Bridgit team: e.g. {\"harness\":\"Claude Code\",\"harness_version\":\"2.3.1\",\"model\":\"Claude Opus 5.5\"} (up to 20 short string, number or boolean fields)",
     "type": "object",
     "required": false
    }
   ],
   "example": "POST /v1/feedback {\"kind\":\"bug\",\"description\":\"image/last returned 500 twice\",\"agent_info\":{\"harness\":\"Claude Code\",\"harness_version\":\"2.3.1\",\"model\":\"Claude Opus 5.5\"}}",
   "idempotent": true,
   "path": "/v1/feedback",
   "capability": "feedback",
   "returns": "id (use in URLs), ref and number (the team-wide number, e.g. A0042; null until the phone sends it to the Bridgit team, then GET url shows it), status, sent_to_team, url (check back with GET), followup_url, team_api {base_url, key_endpoint, docs}",
   "errors": [
    "forbidden",
    "invalid_parameter",
    "rate_limited"
   ],
   "requires": null
  },
  {
   "summary": "Send the owner a notice, a question (buttons, text reply, choice, links), an HTML card or a live text panel. Prefer a native question for simple choices, normal priority and no speech. Sending isn't seeing or answering: wait with GET /v1/interactions/{id}?wait=55 (or one /v1/events loop for several)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "interactions_send",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "kind",
     "description": "notice (one-way), question, html (a card you draw; answer comes from Bridgit.respond) or text (a live text panel you stream into with /v1/text/stream)",
     "type": "string",
     "required": true,
     "enum": [
      "notice",
      "question",
      "html",
      "text"
     ]
    },
    {
     "name": "title",
     "description": "Up to 80 characters",
     "type": "string",
     "required": true
    },
    {
     "name": "text",
     "description": "Up to 2,000 characters",
     "type": "string",
     "required": false
    },
    {
     "name": "image",
     "description": "{\"gallery_id\"} or {\"data_base64\"}, up to 2 MB",
     "type": "object",
     "required": false
    },
    {
     "name": "html",
     "description": "kind=html: the card's HTML, up to 256 KB. Runs offline: no network, only data:image/* images; call Bridgit.respond(value) once per question view (up to 16 KB) to answer; value is a JSON string or a plain object/array, which is serialized for you. window.Bridget is the same object (the only name on 1.6.269 and older). After persistence, bridgit:answered lets the card show its confirmation; the trusted Back to questions control reloads the question for resubmission. Bridgit.act({method, path, query?, body?}) runs one of your own endpoints on the phone right away, as you, with your grants and the owner's switches, e.g. await Bridgit.act({path:'/v1/flashlight', body:{on:true, auto_off_seconds:1}}) resolves to {ok, status, body}; you get an action.executed event for each call, so it works while you're offline. A single card can't be swiped: show progress as text (\"Step 2 of 5\"), not swipe or segment bars. Complete pages: GET /v1/samples",
     "type": "string",
     "required": false
    },
    {
     "name": "controls",
     "description": "Question: 1-6 of {type: button|text|choice|link, id, label, style?, placeholder?, options?, url?, package?}. Notice: links only",
     "type": "array",
     "required": false
    },
    {
     "name": "priority",
     "description": "normal or urgent",
     "type": "string",
     "required": false,
     "enum": [
      "normal",
      "urgent"
     ]
    },
    {
     "name": "speak",
     "description": "Read it aloud on the phone",
     "type": "boolean",
     "required": false
    },
    {
     "name": "expires_in_seconds",
     "description": "60 to 604800, default 86400. After it, waits return expired and you get interaction.expired, but a question stays on the owner's phone and they can still answer it late (interaction.answered with late=true)",
     "type": "integer",
     "required": false
    },
    {
     "name": "stack",
     "description": "kind=html: 1-32 letters, digits, - or _. Your cards with the same stack id form one stack the owner swipes through (drag left = next, right = previous) with \"n of m\" and a peek of the next card; each card keeps its own id, wait and answer. Up to 10 cards per stack id; an open stack counts as one of your 5 open questions. Leave it out for a single card, which is never swipeable. Don't rely on horizontal scrolling inside a stacked card. Wait on GET /v1/events?types=interaction.answered rather than one wait per card",
     "type": "string",
     "required": false
    },
    {
     "name": "stack_index",
     "description": "kind=html with stack: position 0-9 (default: after the cards already posted)",
     "type": "integer",
     "required": false
    },
    {
     "name": "swipe_answer",
     "description": "kind=html with stack: {\"left\": label, \"right\": label} (1-20 characters each). While the card waits, a swipe answers it and moves on: answer.data = {\"swipe\": \"left\"|\"right\", \"label\": ...}. Bridgit shows both labels under the card",
     "type": "object",
     "required": false
    }
   ],
   "example": "POST /v1/interactions {\"kind\":\"question\",\"title\":\"Pay this invoice?\",\"controls\":[{\"type\":\"button\",\"id\":\"yes\",\"label\":\"Pay\",\"style\":\"primary\"},{\"type\":\"button\",\"id\":\"no\",\"label\":\"Not now\"}]}",
   "idempotent": true,
   "path": "/v1/interactions",
   "capability": "interactions",
   "returns": "interaction_id, status, expires_at, display {shown_as: full_screen|bridgit_open|notification, full_screen, missing?: [open_on_arrival, display_over_other_apps], fix?}. When full_screen is false and missing is set, only a notification was shown; pass fix on to the owner",
   "errors": [
    "capability_disabled",
    "notifications_required",
    "invalid_parameter",
    "payload_too_large",
    "too_many_open",
    "photo_not_found"
   ],
   "requires": null
  },
  {
   "summary": "Your interactions, newest first",
   "method": "GET",
   "auth": true,
   "mcp_tool": "interactions_list",
   "query": [
    {
     "name": "status",
     "description": "pending, seen, answered, dismissed, expired or cancelled",
     "type": "string",
     "required": false
    },
    {
     "name": "limit",
     "description": "1-100, default 50",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/interactions?status=answered",
   "idempotent": false,
   "path": "/v1/interactions",
   "capability": "interactions",
   "returns": "interactions[]",
   "errors": [],
   "requires": null
  },
  {
   "summary": "One of your interactions; with wait, holds until the owner answers or closes it",
   "method": "GET",
   "auth": true,
   "mcp_tool": "interactions_get",
   "query": [{
    "name": "wait",
    "description": "Hold up to this many seconds (max 55) for the answer",
    "type": "integer",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/interactions/12?wait=55",
   "idempotent": false,
   "path": "/v1/interactions/{id}",
   "capability": "interactions",
   "returns": "kind, status, answer{button, values} (html cards: answer{button: null, values: {}, data: what the page sent}), answered_at, closed_at",
   "errors": [
    "not_found",
    "too_many_waits"
   ],
   "requires": null
  },
  {
   "summary": "Cancel one of your interactions",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "interactions_cancel",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/interactions/12",
   "idempotent": false,
   "path": "/v1/interactions/{id}",
   "capability": "interactions",
   "returns": "the interaction",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Your events, oldest first: answers, closes, expiries, switch changes. Repeated phone answers emit interaction.answered with resubmission=true and previous_answer; agents decide whether to accept the change. An answer given after the question expired comes as interaction.answered with late=true. action.executed: one of your HTML cards ran Bridgit.act (interaction_id, method, path, status, ok, error)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "events",
   "query": [
    {
     "name": "since",
     "description": "next_cursor from the last call; omit for the last 50",
     "type": "string",
     "required": false
    },
    {
     "name": "wait",
     "description": "With since: hold up to 55 s for a new event",
     "type": "integer",
     "required": false
    },
    {
     "name": "types",
     "description": "Comma list, e.g. interaction.answered",
     "type": "string",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/events?since=0&wait=55",
   "idempotent": false,
   "path": "/v1/events",
   "capability": "interactions",
   "returns": "events[id, type, at, data], next_cursor",
   "errors": [
    "invalid_parameter",
    "too_many_waits"
   ],
   "requires": null
  },
  {
   "summary": "Add text to your live text panel (an interaction of kind text); it scrolls on the phone as it arrives",
   "method": "POST",
   "auth": true,
   "mcp_tool": "text_stream",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "interaction_id",
     "description": "The panel's interaction_id",
     "type": "string",
     "required": true
    },
    {
     "name": "append",
     "description": "Text to add, up to 16 KB per call (the panel keeps the last 200 KB)",
     "type": "string",
     "required": false
    },
    {
     "name": "done",
     "description": "true: the stream is finished; the panel stays until the owner closes it",
     "type": "boolean",
     "required": false
    }
   ],
   "example": "POST /v1/text/stream {\"interaction_id\":\"42\",\"append\":\"Claude: running the tests now...\\n\"}",
   "idempotent": false,
   "path": "/v1/text/stream",
   "capability": "interactions",
   "returns": "interaction_id, length, status",
   "errors": [
    "not_found",
    "invalid_parameter",
    "conflict",
    "rate_limited"
   ],
   "requires": null
  },
  {
   "summary": "Send a signed test event to the callback URL the owner set for you (EVT-05/06)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "callback_test",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "POST /v1/callback/test",
   "idempotent": false,
   "path": "/v1/callback/test",
   "capability": "interactions",
   "returns": "delivered, http_status, host",
   "errors": [
    "not_found",
    "callback_failed"
   ],
   "requires": null
  },
  {
   "summary": "Model Context Protocol (Streamable HTTP, JSON replies): every endpoint you can use is an MCP tool",
   "method": "POST",
   "auth": true,
   "mcp_tool": null,
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "jsonrpc",
     "description": "\"2.0\"",
     "type": "string",
     "required": true
    },
    {
     "name": "method",
     "description": "initialize, tools/list, tools/call, ping",
     "type": "string",
     "required": true
    },
    {
     "name": "params",
     "description": "For tools/call: {name, arguments}",
     "type": "object",
     "required": false
    },
    {
     "name": "id",
     "description": "Request id",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/mcp {\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}",
   "idempotent": false,
   "path": "/v1/mcp",
   "capability": "status",
   "returns": "JSON-RPC result",
   "errors": ["invalid_parameter"],
   "requires": null
  },
  {
   "summary": "Where the phone is now, with altitude, speed, heading and the compass",
   "method": "GET",
   "auth": true,
   "mcp_tool": "location",
   "query": [
    {
     "name": "fresh",
     "description": "true (default): a new fix, up to 15 s; false: the last known one at once",
     "type": "boolean",
     "required": false
    },
    {
     "name": "accuracy",
     "description": "high (GPS, default) or balanced",
     "type": "string",
     "required": false,
     "enum": [
      "high",
      "balanced"
     ]
    },
    {
     "name": "compass",
     "description": "Include the compass (default true)",
     "type": "boolean",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": true,
   "body": [],
   "example": "GET /v1/location",
   "idempotent": false,
   "path": "/v1/location",
   "capability": "location",
   "returns": "latitude, longitude, altitude_m, accuracy_m, vertical_accuracy_m, speed_mps, bearing_deg, fix_time, age_s, precision, compass{azimuth_deg, direction, pitch_deg, roll_deg}",
   "errors": [
    "capability_disabled",
    "permission_denied",
    "notifications_required",
    "capture_timeout",
    "service_start_blocked"
   ],
   "requires": null
  },
  {
   "summary": "Play audio on the phone's speaker: send audio/* bytes as the body, or {\"url\"} (public https)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "speaker_play",
   "query": [
    {
     "name": "replace",
     "description": "after_current (default: replace what's queued from you), now, or queue",
     "type": "string",
     "required": false,
     "enum": [
      "after_current",
      "now",
      "queue"
     ]
    },
    {
     "name": "tour_id",
     "description": "Narration for your running City tour",
     "type": "string",
     "required": false
    },
    {
     "name": "urgent",
     "description": "Jump the queue (answers to questions)",
     "type": "boolean",
     "required": false
    },
    {
     "name": "text",
     "description": "Caption shown on the phone while it plays",
     "type": "string",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [{
    "name": "url",
    "description": "https:// audio file (MP3, M4A, OGG, WAV, FLAC), up to 25 MB",
    "type": "string",
    "required": false
   }],
   "example": "POST /v1/speaker/play (Content-Type: audio/mpeg, body = the MP3)",
   "idempotent": true,
   "path": "/v1/speaker/play",
   "capability": "speaker",
   "returns": "play_id, position, status (playing, queued or silenced)",
   "errors": [
    "capability_disabled",
    "notifications_required",
    "invalid_parameter",
    "payload_too_large",
    "rate_limited",
    "not_found"
   ],
   "requires": null
  },
  {
   "summary": "Read text aloud on the phone (its own text-to-speech)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "speaker_say",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "text",
     "description": "Up to 4,000 characters",
     "type": "string",
     "required": true
    },
    {
     "name": "language",
     "description": "e.g. en-GB, de-DE",
     "type": "string",
     "required": false
    },
    {
     "name": "rate",
     "description": "0.5-2, default 1",
     "type": "number",
     "required": false
    },
    {
     "name": "pitch",
     "description": "0.5-2, default 1",
     "type": "number",
     "required": false
    },
    {
     "name": "replace",
     "description": "after_current (default), now, or queue",
     "type": "string",
     "required": false
    },
    {
     "name": "tour_id",
     "description": "Narration for your City tour",
     "type": "string",
     "required": false
    },
    {
     "name": "urgent",
     "description": "Jump the queue",
     "type": "boolean",
     "required": false
    }
   ],
   "example": "POST /v1/speaker/say {\"text\":\"On your left is the old town hall, built in 1360.\",\"tour_id\":\"t123\"}",
   "idempotent": true,
   "path": "/v1/speaker/say",
   "capability": "speaker",
   "returns": "play_id, position, status (playing, queued, or silenced: the phone is on silent, Do Not Disturb or in a call, so it won't be heard)",
   "errors": [
    "capability_disabled",
    "invalid_parameter",
    "rate_limited",
    "not_found"
   ],
   "requires": null
  },
  {
   "summary": "What's playing, and your queued sounds",
   "method": "GET",
   "auth": true,
   "mcp_tool": "speaker_status",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/speaker",
   "idempotent": false,
   "path": "/v1/speaker",
   "capability": "speaker",
   "returns": "playing{play_id, agent, text} (yours only, else null), busy (someone else's sound is playing), paused, queued[], quiet",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Stop your audio and clear your queue",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "speaker_stop",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/speaker",
   "idempotent": false,
   "path": "/v1/speaker",
   "capability": "speaker",
   "returns": "stopped",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Your running City tour, if the owner started one with you (D30)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "tour_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/tour",
   "idempotent": false,
   "path": "/v1/tour",
   "capability": "tour",
   "returns": "tour{tour_id, kinds, interval_s, started_at, ticks, last_tick_at, distance_m, photos_sent, brief} or null",
   "errors": [],
   "requires": null
  },
  {
   "summary": "A photo the owner took with Take photo on your tour (n from tour.photo), sized for you",
   "method": "GET",
   "auth": true,
   "mcp_tool": "tour_photo",
   "query": [
    {
     "name": "size",
     "description": "Preset: agent (1568 px, q85), thumb (512, q75), large (2560, q90), original",
     "type": "string",
     "required": false,
     "enum": [
      "agent",
      "thumb",
      "large",
      "original"
     ]
    },
    {
     "name": "max_edge_px",
     "description": "Long edge limit, 64-20000; overrides the preset",
     "type": "integer",
     "required": false
    },
    {
     "name": "max_width_px",
     "description": "Width limit, 64-20000",
     "type": "integer",
     "required": false
    },
    {
     "name": "quality",
     "description": "JPEG quality 40-100",
     "type": "integer",
     "required": false
    },
    {
     "name": "raw",
     "description": "1 = return the image bytes, details in X-Bridgit-* headers",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/tour/photo/1?size=agent",
   "idempotent": false,
   "path": "/v1/tour/photo/{n}",
   "capability": "tour",
   "returns": "taken_at, width_px, height_px, format, size_bytes, data_base64",
   "errors": [
    "not_found",
    "photo_not_found",
    "payload_too_large"
   ],
   "requires": null
  },
  {
   "summary": "End your City tour",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "tour_end",
   "query": [{
    "name": "tour_id",
    "description": "Only end this tour (it must be yours and still running)",
    "type": "string",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/tour",
   "idempotent": false,
   "path": "/v1/tour",
   "capability": "tour",
   "returns": "ended",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Tell the owner which model you are and what runs you; shown on your agent card (AGT-16)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "me",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "model",
     "description": "Your model, as exact as you know it, e.g. claude-opus-5-5 or gpt-6.1 (1-60 chars)",
     "type": "string",
     "required": true
    },
    {
     "name": "harness",
     "description": "The program running you, e.g. Claude Code, Codex CLI, a Python script (1-40 chars)",
     "type": "string",
     "required": false
    }
   ],
   "example": "POST /v1/me {\"model\":\"claude-opus-5-5\",\"harness\":\"Claude Code\"}",
   "idempotent": false,
   "path": "/v1/me",
   "capability": "status",
   "returns": "agent, model, harness",
   "errors": ["invalid_parameter"],
   "requires": null
  },
  {
   "summary": "Whether the owner has verified their email. Bridgit works for 24 hours from the first agent call; after that every other endpoint answers email_required until they do",
   "method": "GET",
   "auth": true,
   "mcp_tool": "owner_email_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/owner/email",
   "idempotent": false,
   "path": "/v1/owner/email",
   "capability": "status",
   "returns": "status (verified, grace or required), state (none, pending or confirmed), email (masked, e.g. w***@gmail.com, or null), grace_ends_at (ISO time, or null before the first call), hours_left, required_after_hours (24), how",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Give the owner's email address: bridgit.cc emails them a link, and tapping it verifies the address (the same address gets email alerts). Works during and after the 24 hours; not once an address is verified (the owner changes it on the phone)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "owner_email_post",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [{
    "name": "email",
    "description": "The owner's email address",
    "type": "string",
    "required": true
   }],
   "example": "POST /v1/owner/email {\"email\":\"owner@example.com\"}",
   "idempotent": false,
   "path": "/v1/owner/email",
   "capability": "status",
   "returns": "status, state (pending), email (masked), next (what to tell the owner)",
   "errors": [
    "invalid_parameter",
    "conflict",
    "rate_limited",
    "upstream_failed"
   ],
   "requires": null
  },
  {
   "summary": "Wake words: whether the phone is listening, and the wake words the owner gave you (CAP-47)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "listen",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/listen",
   "idempotent": false,
   "path": "/v1/listen",
   "capability": "wakewords",
   "returns": "enabled, state (off|listening|hearing|paused), reason, muted_until, model (ready|not_downloaded|downloading|failed), model_name, max_seconds, wake_words[{phrase, send_clip}]. Each hit arrives as a listen.heard event: wake_word, transcript, heard_at, ended_by (pause|max_seconds|no_speech|interrupted), duration_seconds, recogniser, clip_id, capture {capture_id, stored, url, memory_until, file}",
   "errors": [],
   "requires": null
  },
  {
   "summary": "The audio after a wake word, when the owner set that wake word to send it (kept in memory 10 minutes unless you chose longer with POST /v1/captures/storage; also GET /v1/captures/{id})",
   "method": "GET",
   "auth": true,
   "mcp_tool": "listen_clip",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/listen/clip/c0123abcd",
   "idempotent": false,
   "path": "/v1/listen/clip/{id}",
   "capability": "wakewords",
   "returns": "heard_at, duration_seconds, format (wav), sample_rate_hz, size_bytes, data_base64 (or raw=1 bytes)",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Listen for any sounds: whether the phone tells you when the room gets loud (8.23)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "sounds",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/sounds",
   "idempotent": false,
   "path": "/v1/sounds",
   "capability": "sounds",
   "returns": "enabled, for_you, state (off|listening|paused), reason, sensitivity (quiet|normal|loud), send_clip, cooldown_seconds, max_seconds. Each sound arrives as a sound.heard event: heard_at, duration_seconds, peak_dbfs, mean_dbfs, sensitivity, clip_id, capture {capture_id, stored, url, memory_until, file}",
   "errors": [],
   "requires": null
  },
  {
   "summary": "The audio of a sound (up to 10 s, from just before it started), when the owner lets sounds send audio (kept in memory 10 minutes unless you chose longer with POST /v1/captures/storage; also GET /v1/captures/{id})",
   "method": "GET",
   "auth": true,
   "mcp_tool": "sounds_clip",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/sounds/clip/c0123abcd",
   "idempotent": false,
   "path": "/v1/sounds/clip/{id}",
   "capability": "sounds",
   "returns": "heard_at, duration_seconds, format (wav), sample_rate_hz, size_bytes, data_base64 (or raw=1 bytes)",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Where your captures go: your choice and what the owner allows (SEC-13)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "captures_storage_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/captures/storage",
   "idempotent": false,
   "path": "/v1/captures/storage",
   "capability": "captures",
   "returns": "yours {store, kinds, upload_url, upload_headers (names only), keep_minutes}, effective {<kind>: {store, keep_minutes}}, owner {agent_files, agent_upload, max_keep_minutes, any_agent_can_fetch, owner_files, owner_upload}, destinations, kinds",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Choose where your captures go, in any combination: memory, phone files, upload; and how long memory keeps them (within the owner's limits)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "captures_storage_set",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "store",
     "description": "Any of memory, files, upload; [] = only return captures; null = back to the defaults (clips in memory, the rest only returned)",
     "type": "array",
     "required": true
    },
    {
     "name": "kinds",
     "description": "Only these kinds: camera, screen, mic, wakewords, sounds; null = all",
     "type": "array",
     "required": false
    },
    {
     "name": "upload_url",
     "description": "Where uploads go (POST, the capture as the body, X-Bridgit-Capture-Id/Kind/Agent/Captured-At headers). Tailnet always; public HTTPS when the owner allows internet callbacks. Needed with upload",
     "type": "string",
     "required": false
    },
    {
     "name": "upload_headers",
     "description": "Up to 8 extra headers for the upload, e.g. {\"Authorization\": \"Bearer ...\"}; never shown back",
     "type": "object",
     "required": false
    },
    {
     "name": "keep_minutes",
     "description": "How long memory keeps them, 1 to the owner's limit (default 10, owner limit 1440 unless changed)",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/captures/storage {\"store\":[\"memory\",\"files\",\"upload\"],\"upload_url\":\"https://example.com/in\",\"keep_minutes\":120}",
   "idempotent": true,
   "path": "/v1/captures/storage",
   "capability": "captures",
   "returns": "as GET /v1/captures/storage",
   "errors": [
    "invalid_parameter",
    "forbidden"
   ],
   "requires": null
  },
  {
   "summary": "Your kept captures (photos, screenshots, recordings, clips), newest first: where each is and until when",
   "method": "GET",
   "auth": true,
   "mcp_tool": "captures_list",
   "query": [
    {
     "name": "kind",
     "description": "Only this kind",
     "type": "string",
     "required": false,
     "enum": [
      "camera",
      "screen",
      "mic",
      "wakewords",
      "sounds"
     ]
    },
    {
     "name": "limit",
     "description": "1-200, default 50",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/captures?kind=camera",
   "idempotent": false,
   "path": "/v1/captures",
   "capability": "captures",
   "returns": "captures[id, kind, agent_id, mime, size_bytes, captured_at, in_memory, memory_until, file, upload {agent|owner: pending|sent|failed: why}, url, meta]",
   "errors": ["invalid_parameter"],
   "requires": null
  },
  {
   "summary": "One kept capture: from memory, or from its phone file after memory let it go",
   "method": "GET",
   "auth": true,
   "mcp_tool": "captures_get",
   "query": [{
    "name": "raw",
    "description": "1 = the bytes, details in X-Bridgit-* headers",
    "type": "integer",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/captures/c0123abcd?raw=1",
   "idempotent": false,
   "path": "/v1/captures/{id}",
   "capability": "captures",
   "returns": "the capture's details (as in the list) and data_base64, or raw=1 bytes",
   "errors": [
    "not_found",
    "payload_too_large"
   ],
   "requires": null
  },
  {
   "summary": "Forget a capture: drops the memory copy and deletes its phone file (uploads can't be recalled)",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "captures_delete",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/captures/c0123abcd",
   "idempotent": false,
   "path": "/v1/captures/{id}",
   "capability": "captures",
   "returns": "deleted",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Every Bridgit setting and the phone settings it manages: value, type, range, and whether you may change it (SET-01)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "settings_get",
   "query": [
    {
     "name": "section",
     "description": "Only this section",
     "type": "string",
     "required": false,
     "enum": [
      "features",
      "listener",
      "privacy",
      "log",
      "alerts",
      "appearance",
      "speaker",
      "listening",
      "captures",
      "notifications",
      "phone",
      "android",
      "agent"
     ]
    },
    {
     "name": "key",
     "description": "Only this setting, e.g. appearance.theme",
     "type": "string",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/settings?section=phone",
   "idempotent": false,
   "path": "/v1/settings",
   "capability": "settings",
   "returns": "can_write, write_blocked_reason, write_blocked_why, sections, settings[key, section, type, value, description, enum, min, max, writable, locked_reason, why]",
   "errors": [
    "invalid_parameter",
    "not_found"
   ],
   "requires": null
  },
  {
   "summary": "Change settings (agents with Agent Super Powers, once the owner turns on App settings); every change is checked first, so one bad change changes nothing (SET-02)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "settings_post",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [{
    "name": "changes",
    "description": "{\"<key>\": <value>, ...}, up to 50; keys and allowed values from GET /v1/settings",
    "type": "object",
    "required": true
   }],
   "example": "POST /v1/settings {\"changes\":{\"appearance.theme\":\"dark\",\"phone.volume_media\":40,\"features.camera\":true}}",
   "idempotent": true,
   "path": "/v1/settings",
   "capability": "settings",
   "returns": "changed[key, from, to], unchanged, settings (as in GET)",
   "errors": [
    "capability_disabled",
    "forbidden",
    "setting_locked",
    "access_needed",
    "invalid_parameter"
   ],
   "requires": null
  },
  {
   "summary": "Request owner-approved MediaProjection screen access",
   "method": "POST",
   "auth": true,
   "mcp_tool": "screen_grant_post",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [{
    "name": "duration_seconds",
    "description": "0=once, 3600=one hour, 57600=sixteen hours",
    "type": "integer",
    "required": false
   }],
   "example": "POST /v1/screen/grant",
   "idempotent": true,
   "path": "/v1/screen/grant",
   "capability": "screen_share",
   "returns": "Request owner-approved MediaProjection screen access",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Your screen requests and active grant",
   "method": "GET",
   "auth": true,
   "mcp_tool": "screen_grants",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/screen/grants",
   "idempotent": false,
   "path": "/v1/screen/grants",
   "capability": "screen_share",
   "returns": "Your screen requests and active grant",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Capture a JPEG using your active owner-approved grant",
   "method": "POST",
   "auth": true,
   "mcp_tool": "screen_share_capture",
   "query": [],
   "developer_only": false,
   "capture": true,
   "body": [],
   "example": "POST /v1/screen/share/capture",
   "idempotent": true,
   "path": "/v1/screen/share/capture",
   "capability": "screen_share",
   "returns": "Capture a JPEG using your active owner-approved grant",
   "errors": [],
   "requires": null
  },
  {
   "summary": "End your screen-sharing grant",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "screen_grant_delete",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/screen/grant",
   "idempotent": false,
   "path": "/v1/screen/grant",
   "capability": "screen_share",
   "returns": "End your screen-sharing grant",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Battery, network, screen and sound status, with as_of",
   "method": "GET",
   "auth": true,
   "mcp_tool": "phone_status",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/phone/status",
   "idempotent": false,
   "path": "/v1/phone/status",
   "capability": "phone",
   "returns": "Battery, network, screen and sound status, with as_of",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Full battery report: percent, status, plug type, health, temperature, voltage, cycles, current, time to full, battery saver, which power settings agents may change, and the battery.* events",
   "method": "GET",
   "auth": true,
   "mcp_tool": "phone_battery",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/phone/battery",
   "idempotent": false,
   "path": "/v1/phone/battery",
   "capability": "phone",
   "returns": "Full battery report: percent, status, plug type, health, temperature, voltage, cycles, current, time to full, battery saver, which power settings agents may change, and the battery.* events",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Screen and lock context; likely=in_use or unknown, with the rule",
   "method": "GET",
   "auth": true,
   "mcp_tool": "phone_context",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/phone/context",
   "idempotent": false,
   "path": "/v1/phone/context",
   "capability": "phone",
   "returns": "Screen and lock context; likely=in_use or unknown, with the rule",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Set Do Not Disturb, with optional restoration",
   "method": "POST",
   "auth": true,
   "mcp_tool": "phone_dnd",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "mode",
     "description": "off, priority, alarms or none (none mutes alarms)",
     "type": "string",
     "required": true
    },
    {
     "name": "duration_minutes",
     "description": "Optional timed restoration, 1\u20131440 minutes; null leaves the change in place",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/phone/dnd",
   "idempotent": true,
   "path": "/v1/phone/dnd",
   "capability": "controls",
   "returns": "Set Do Not Disturb, with optional restoration",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Change ringer or volume",
   "method": "POST",
   "auth": true,
   "mcp_tool": "phone_sound",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "ringer_mode",
     "description": "normal, vibrate or silent",
     "type": "string",
     "required": false
    },
    {
     "name": "volume",
     "description": "media/ring/alarm percentages, 0\u2013100",
     "type": "object",
     "required": false
    },
    {
     "name": "duration_minutes",
     "description": "Optional timed restoration, 1\u20131440 minutes; null leaves the change in place",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/phone/sound",
   "idempotent": true,
   "path": "/v1/phone/sound",
   "capability": "controls",
   "returns": "Change ringer or volume",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Set brightness or automatic brightness",
   "method": "POST",
   "auth": true,
   "mcp_tool": "phone_brightness",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "level",
     "description": "0\u20131",
     "type": "number",
     "required": false
    },
    {
     "name": "auto",
     "description": "true enables automatic brightness",
     "type": "boolean",
     "required": false
    },
    {
     "name": "duration_minutes",
     "description": "Optional timed restoration, 1\u20131440 minutes; null leaves the change in place",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/phone/brightness",
   "idempotent": true,
   "path": "/v1/phone/brightness",
   "capability": "controls",
   "returns": "Set brightness or automatic brightness",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Turn battery saver on or off (needs a one-time USB grant, see /phone/battery controls) or set the screen timeout, with optional restoration",
   "method": "POST",
   "auth": true,
   "mcp_tool": "phone_power",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "battery_saver",
     "description": "true turns battery saver on; Android keeps it off while charging",
     "type": "boolean",
     "required": false
    },
    {
     "name": "screen_timeout_seconds",
     "description": "15\u20131800",
     "type": "integer",
     "required": false
    },
    {
     "name": "duration_minutes",
     "description": "Optional timed restoration, 1\u20131440 minutes; null leaves the change in place",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/phone/power",
   "idempotent": true,
   "path": "/v1/phone/power",
   "capability": "controls",
   "returns": "Turn battery saver on or off (needs a one-time USB grant, see /phone/battery controls) or set the screen timeout, with optional restoration",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Restore pending timed phone changes, preserving subsequent manual changes",
   "method": "POST",
   "auth": true,
   "mcp_tool": "phone_undo",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "POST /v1/phone/undo",
   "idempotent": true,
   "path": "/v1/phone/undo",
   "capability": "controls",
   "returns": "Restore pending timed phone changes, preserving subsequent manual changes",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Whether the torch is on",
   "method": "GET",
   "auth": true,
   "mcp_tool": "flashlight_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/flashlight",
   "idempotent": false,
   "path": "/v1/flashlight",
   "capability": "flashlight",
   "returns": "Whether the torch is on",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Set torch with mandatory auto-off",
   "method": "POST",
   "auth": true,
   "mcp_tool": "flashlight_post",
   "query": [],
   "developer_only": false,
   "capture": true,
   "body": [
    {
     "name": "on",
     "description": "true or false",
     "type": "boolean",
     "required": true
    },
    {
     "name": "auto_off_seconds",
     "description": "1\u2013600, default 60",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/flashlight",
   "idempotent": true,
   "path": "/v1/flashlight",
   "capability": "flashlight",
   "returns": "Set torch with mandatory auto-off",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Ring and vibrate within Android sound restrictions",
   "method": "POST",
   "auth": true,
   "mcp_tool": "phone_ring_post",
   "query": [],
   "developer_only": false,
   "capture": true,
   "body": [{
    "name": "duration_seconds",
    "description": "1\u201360, default 15",
    "type": "integer",
    "required": false
   }],
   "example": "POST /v1/phone/ring",
   "idempotent": true,
   "path": "/v1/phone/ring",
   "capability": "ring",
   "returns": "Ring and vibrate within Android sound restrictions",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Stop ringing and vibration",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "phone_ring_delete",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/phone/ring",
   "idempotent": false,
   "path": "/v1/phone/ring",
   "capability": "ring",
   "returns": "Stop ringing and vibration",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Supported fresh sensor values or explicit unavailable status",
   "method": "GET",
   "auth": true,
   "mcp_tool": "sensors",
   "query": [{
    "name": "which",
    "description": "Comma separated: light,pressure,proximity,steps,orientation,motion",
    "type": "string",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/sensors",
   "idempotent": false,
   "path": "/v1/sensors",
   "capability": "sensors",
   "returns": "Supported fresh sensor values or explicit unavailable status",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Up to 100 active notification texts since service connected; RAM only",
   "method": "GET",
   "auth": true,
   "mcp_tool": "notifications",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/notifications",
   "idempotent": false,
   "path": "/v1/notifications",
   "capability": "notifications",
   "returns": "Up to 100 active notification texts since service connected; RAM only",
   "errors": [],
   "requires": null
  },
  {
   "summary": "App foreground time from Android usage buckets (maximum seven days)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "usage",
   "query": [
    {
     "name": "since",
     "description": "ISO-8601 UTC",
     "type": "string",
     "required": false
    },
    {
     "name": "until",
     "description": "ISO-8601 UTC",
     "type": "string",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/usage",
   "idempotent": false,
   "path": "/v1/usage",
   "capability": "usage",
   "returns": "App foreground time from Android usage buckets (maximum seven days)",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Read-only steps, sleep or heart-rate source records (maximum 100)",
   "method": "GET",
   "auth": true,
   "mcp_tool": "health_records",
   "query": [
    {
     "name": "type",
     "description": "steps, sleep, heart_rate",
     "type": "string",
     "required": false
    },
    {
     "name": "since",
     "description": "ISO-8601 UTC",
     "type": "string",
     "required": false
    },
    {
     "name": "until",
     "description": "ISO-8601 UTC",
     "type": "string",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/health/records",
   "idempotent": false,
   "path": "/v1/health/records",
   "capability": "health_connect",
   "returns": "Read-only steps, sleep or heart-rate source records (maximum 100)",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Queue a five-minute owner approval; never copies until owner taps Copy",
   "method": "POST",
   "auth": true,
   "mcp_tool": "clipboard_post",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [{
    "name": "text",
    "description": "1\u201310000 characters",
    "type": "string",
    "required": true
   }],
   "example": "POST /v1/clipboard",
   "idempotent": true,
   "path": "/v1/clipboard",
   "capability": "clipboard",
   "returns": "Queue a five-minute owner approval; never copies until owner taps Copy",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Your clipboard request: pending, copied, denied or expired",
   "method": "GET",
   "auth": true,
   "mcp_tool": "clipboard_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/clipboard/{id}",
   "idempotent": false,
   "path": "/v1/clipboard/{id}",
   "capability": "clipboard",
   "returns": "Your clipboard request: pending, copied, denied or expired",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Record an MP4, returned raw (HTTP: 40 MB maximum; MCP: 6 MB, may stop early)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "camera_video",
   "query": [],
   "developer_only": false,
   "capture": true,
   "body": [
    {
     "name": "camera",
     "description": "rear or front",
     "type": "string",
     "required": false
    },
    {
     "name": "duration_seconds",
     "description": "1\u201330, default 10",
     "type": "integer",
     "required": false
    },
    {
     "name": "audio",
     "description": "false by default; true also requires microphone grant",
     "type": "boolean",
     "required": false
    },
    {
     "name": "resolution",
     "description": "720p or 1080p",
     "type": "string",
     "required": false
    }
   ],
   "example": "POST /v1/camera/video",
   "idempotent": true,
   "path": "/v1/camera/video",
   "capability": "video",
   "returns": "Record an MP4, returned raw (HTTP: 40 MB maximum; MCP: 6 MB, may stop early)",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Accessible videos and metadata",
   "method": "GET",
   "auth": true,
   "mcp_tool": "gallery_videos_recent",
   "query": [{
    "name": "limit",
    "description": "1\u2013100, default 20",
    "type": "integer",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/gallery/videos/recent",
   "idempotent": false,
   "path": "/v1/gallery/videos/recent",
   "capability": "gallery_video",
   "returns": "Accessible videos and metadata",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Accessible video raw, or a JPEG frame",
   "method": "GET",
   "auth": true,
   "mcp_tool": "gallery_video",
   "query": [{
    "name": "frame_at_s",
    "description": "Optional frame position in seconds",
    "type": "number",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/gallery/video/{id}",
   "idempotent": false,
   "path": "/v1/gallery/video/{id}",
   "capability": "gallery_video",
   "returns": "Accessible video raw, or a JPEG frame",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Recognize Latin-script text locally; no image upload",
   "method": "POST",
   "auth": true,
   "mcp_tool": "ocr",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "gallery_id",
     "description": "Accessible gallery image (also requires gallery grant)",
     "type": "integer",
     "required": false
    },
    {
     "name": "data_base64",
     "description": "Image bytes instead of gallery_id",
     "type": "string",
     "required": false
    }
   ],
   "example": "POST /v1/ocr",
   "idempotent": true,
   "path": "/v1/ocr",
   "capability": "ocr",
   "returns": "Recognize Latin-script text locally; no image upload",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Your files transfers, retained 30 days on the phone",
   "method": "GET",
   "auth": true,
   "mcp_tool": "files_list",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/files",
   "idempotent": false,
   "path": "/v1/files",
   "capability": "files",
   "returns": "Your files transfers, retained 30 days on the phone",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Read your selected transfer bytes",
   "method": "GET",
   "auth": true,
   "mcp_tool": "files_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/files/{id}",
   "idempotent": false,
   "path": "/v1/files/{id}",
   "capability": "files",
   "returns": "Read your selected transfer bytes",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Delete your transfer",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "files_delete",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/files/{id}",
   "idempotent": false,
   "path": "/v1/files/{id}",
   "capability": "files",
   "returns": "Delete your transfer",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Receive a file on the phone (25 MB maximum)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "files_receive",
   "query": [{
    "name": "filename",
    "description": "Filename for raw-body uploads (or name= for HTTP compatibility)",
    "type": "string",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "name",
     "description": "Filename, no path",
     "type": "string",
     "required": true
    },
    {
     "name": "mime",
     "description": "MIME type",
     "type": "string",
     "required": false
    },
    {
     "name": "data_base64",
     "description": "File bytes; or send raw body with ?name=...",
     "type": "string",
     "required": false
    }
   ],
   "example": "POST /v1/files",
   "idempotent": true,
   "path": "/v1/files",
   "capability": "files",
   "returns": "Receive a file on the phone (25 MB maximum)",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Your shares transfers, retained 30 days on the phone",
   "method": "GET",
   "auth": true,
   "mcp_tool": "shares_list",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/shares",
   "idempotent": false,
   "path": "/v1/shares",
   "capability": "shares",
   "returns": "Your shares transfers, retained 30 days on the phone",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Read your selected transfer bytes",
   "method": "GET",
   "auth": true,
   "mcp_tool": "shares_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/shares/{id}",
   "idempotent": false,
   "path": "/v1/shares/{id}",
   "capability": "shares",
   "returns": "Read your selected transfer bytes",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Delete your transfer",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "shares_delete",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/shares/{id}",
   "idempotent": false,
   "path": "/v1/shares/{id}",
   "capability": "shares",
   "returns": "Delete your transfer",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Your downloads transfers, retained 30 days on the phone",
   "method": "GET",
   "auth": true,
   "mcp_tool": "downloads_list",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/downloads",
   "idempotent": false,
   "path": "/v1/downloads",
   "capability": "downloads",
   "returns": "Your downloads transfers, retained 30 days on the phone",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Read your selected transfer bytes",
   "method": "GET",
   "auth": true,
   "mcp_tool": "downloads_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/downloads/{id}",
   "idempotent": false,
   "path": "/v1/downloads/{id}",
   "capability": "downloads",
   "returns": "Read your selected transfer bytes",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Delete your transfer",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "downloads_delete",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/downloads/{id}",
   "idempotent": false,
   "path": "/v1/downloads/{id}",
   "capability": "downloads",
   "returns": "Delete your transfer",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Hand the phone an action manifest once: any action it can take (any /v1 call you're allowed, plus download, play, show, send and delete of saved files), each with its own schedule. The owner approves each new manifest once on the phone; then it runs by itself and results arrive as schedule.* events",
   "method": "POST",
   "auth": true,
   "mcp_tool": "schedule_set",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "manifest",
     "description": "{timezone?, actions:[{id, name?, when, do, save_as?, send_to?}]} (see GET /v1/guide → schedule)",
     "type": "object",
     "required": false
    },
    {
     "name": "url",
     "description": "Instead of manifest: a public https:// URL the phone polls for the manifest (ETag honoured)",
     "type": "string",
     "required": false
    },
    {
     "name": "poll_minutes",
     "description": "With url: how often to check, 15-1440 (default 60)",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/schedule {\"manifest\":{\"actions\":[{\"id\":\"pics\",\"when\":{\"start\":\"2026-11-02T16:34\",\"every_minutes\":7,\"count\":3},\"do\":{\"call\":\"POST /v1/camera/snapshot\",\"query\":{\"raw\":\"1\"}},\"send_to\":{\"url\":\"https://example.com/in\"}}]}}",
   "idempotent": true,
   "path": "/v1/schedule",
   "capability": "schedule",
   "returns": "status (awaiting_owner, or active when identical to the running manifest), digest, actions[{id, summary}]",
   "errors": [
    "capability_disabled",
    "invalid_parameter",
    "agent_not_allowed"
   ],
   "requires": null
  },
  {
   "summary": "Your manifest: what's approved, what's waiting for the owner, each action's next run and last result, and your saved files",
   "method": "GET",
   "auth": true,
   "mcp_tool": "schedule_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/schedule",
   "idempotent": false,
   "path": "/v1/schedule",
   "capability": "schedule",
   "returns": "status (none, awaiting_owner, active, paused), timezone, source{url, poll_minutes, last_poll, last_error}, active{digest, actions[{id, summary, next_at, runs, last_status, last_error}]}, pending{digest, actions}, assets[]",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Run one approved action now, outside its timetable (its 'after' chain doesn't follow)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "schedule_run",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "POST /v1/schedule/run/pics",
   "idempotent": false,
   "path": "/v1/schedule/run/{id}",
   "capability": "schedule",
   "returns": "action_id, status (ok or failed), result{runs, last_status, last_error}",
   "errors": [
    "not_found",
    "capability_disabled"
   ],
   "requires": null
  },
  {
   "summary": "Pause or resume your approved actions",
   "method": "POST",
   "auth": true,
   "mcp_tool": "schedule_pause",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [{
    "name": "paused",
    "description": "true pauses, false resumes",
    "type": "boolean",
    "required": true
   }],
   "example": "POST /v1/schedule/pause {\"paused\":true}",
   "idempotent": false,
   "path": "/v1/schedule/pause",
   "capability": "schedule",
   "returns": "paused",
   "errors": [
    "not_found",
    "invalid_parameter"
   ],
   "requires": null
  },
  {
   "summary": "Remove your manifest (approved and waiting) and, unless keep_assets=true, your saved files",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "schedule_remove",
   "query": [{
    "name": "keep_assets",
    "description": "Keep saved files (default false)",
    "type": "boolean",
    "required": false
   }],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/schedule",
   "idempotent": false,
   "path": "/v1/schedule",
   "capability": "schedule",
   "returns": "removed",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Files your actions saved (downloads and kept results), up to 100 / 100 MB, kept 30 days",
   "method": "GET",
   "auth": true,
   "mcp_tool": "schedule_assets_list",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/schedule/assets",
   "idempotent": false,
   "path": "/v1/schedule/assets",
   "capability": "schedule",
   "returns": "assets[{name, mime, size, sha256, created_at, by}]",
   "errors": [],
   "requires": null
  },
  {
   "summary": "One saved file's bytes, e.g. a photo a scheduled action took",
   "method": "GET",
   "auth": true,
   "mcp_tool": "schedule_asset_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/schedule/assets/garden",
   "idempotent": false,
   "path": "/v1/schedule/assets/{name}",
   "capability": "schedule",
   "returns": "The raw bytes with their Content-Type",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Delete one saved file",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "schedule_asset_delete",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/schedule/assets/garden",
   "idempotent": false,
   "path": "/v1/schedule/assets/{name}",
   "capability": "schedule",
   "returns": "deleted",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Weather where the phone is (or at a lat/lon you give): conditions now, the next hours and the next 24 hours, from MET Norway. Schedule it like any call: {\"do\":{\"call\":\"GET /v1/weather\"}}",
   "method": "GET",
   "auth": true,
   "mcp_tool": "weather",
   "query": [
    {
     "name": "lat",
     "description": "Latitude. With lon: weather there, and no location access is needed",
     "type": "number",
     "required": false
    },
    {
     "name": "lon",
     "description": "Longitude, with lat",
     "type": "number",
     "required": false
    },
    {
     "name": "hours",
     "description": "Hourly forecast points after now, 0-48 (default 6)",
     "type": "integer",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/weather?hours=3",
   "idempotent": false,
   "path": "/v1/weather",
   "capability": "weather",
   "returns": "location{latitude, longitude, from (phone or request), rounded_to_decimals}, now{time, temperature_c, symbol, summary, precipitation_mm, precipitation_period, wind_speed_mps, wind_from_deg, wind_from, humidity_pct, cloud_pct, pressure_hpa}, next_hours[{time, temperature_c, symbol, summary, precipitation_mm, wind_speed_mps}], next_12_hours{symbol, summary}, next_24_hours{min_temperature_c, max_temperature_c, precipitation_mm}, forecast_updated_at, fetched_at, expires_at, cached, stale, source, attribution",
   "errors": [
    "capability_disabled",
    "invalid_parameter",
    "agent_not_allowed",
    "permission_denied",
    "access_needed",
    "notifications_required",
    "capture_timeout",
    "upstream_failed"
   ],
   "requires": null
  },
  {
   "summary": "How to help the owner turn on ADB for Bridgit, and what to do once it's on (Markdown)",
   "method": "GET",
   "auth": false,
   "mcp_tool": "adb_setup",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/adb/setup",
   "idempotent": false,
   "path": "/v1/adb/setup",
   "capability": "adb",
   "returns": "text/markdown",
   "errors": [],
   "requires": null
  },
  {
   "summary": "ADB setup progress: developer options, wireless debugging, paired, grants",
   "method": "GET",
   "auth": true,
   "mcp_tool": "adb_status",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/adb/status",
   "idempotent": false,
   "path": "/v1/adb/status",
   "capability": "adb",
   "returns": "developer_options, usb_debugging, wireless_debugging, paired, connected, shell_switch, ready, blocked_reason, grants[key, title, granted]",
   "errors": [],
   "requires": null
  },
  {
   "summary": "Run a shell command as Android's shell user (agents with Agent Super Powers)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "adb_shell",
   "query": [],
   "developer_only": true,
   "capture": false,
   "body": [
    {
     "name": "command",
     "description": "Shell command, 1-8000 characters",
     "type": "string",
     "required": true
    },
    {
     "name": "timeout_seconds",
     "description": "1-120, default 30",
     "type": "integer",
     "required": false
    }
   ],
   "example": "POST /v1/adb/shell {\"command\":\"input keyevent KEYCODE_HOME\"}",
   "idempotent": false,
   "path": "/v1/adb/shell",
   "capability": "adb",
   "returns": "exit_code, output, truncated, timed_out",
   "errors": [
    "capability_disabled",
    "service_start_blocked",
    "forbidden",
    "invalid_parameter"
   ],
   "requires": "super_powers"
  },
  {
   "summary": "Give Bridgit every useful permission and access through ADB (agents with Agent Super Powers)",
   "method": "POST",
   "auth": true,
   "mcp_tool": "adb_grant",
   "query": [],
   "developer_only": true,
   "capture": false,
   "body": [{
    "name": "only",
    "description": "Grant keys to run (default all); see grants in /v1/adb/status",
    "type": "array",
    "required": false
   }],
   "example": "POST /v1/adb/grant {}",
   "idempotent": false,
   "path": "/v1/adb/grant",
   "capability": "adb",
   "returns": "results[], grants[key, title, granted]",
   "errors": [
    "capability_disabled",
    "service_start_blocked",
    "forbidden"
   ],
   "requires": "super_powers"
  },
  {
   "summary": "Ask Jev (TypeSafe) typed questions about a state you send; get typed answers with calibrated probabilities, and the phone runs the conditional actions whose conditions held",
   "method": "POST",
   "auth": true,
   "mcp_tool": "judgment",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "state",
     "description": "What the questions are about: text or JSON, up to about 512 KB. Text only: no images, audio or video (send a transcript)",
     "type": "string|object|array",
     "required": true
    },
    {
     "name": "questions",
     "description": "1-50 typed questions, {id: {type: choice|noul|..., instructions, criteria?}}, as https://docs.typesafe.ai/primitives",
     "type": "object",
     "required": true
    },
    {
     "name": "actions",
     "description": "Up to 10 rules, bridgit-actions/1: {id, when: condition or 1-5 conditions (all must hold), do: action}. Condition: {answer, choice, min_confidence?} or {answer, min?, max?} for a probability. Action: {type: notice, title, text?} (a card from you), {type: speak, text} or {type: log, message}; plain text only, no links",
     "type": "array",
     "required": false
    }
   ],
   "example": "POST /v1/judgment {\"state\":{\"battery_percent\":12,\"charging\":false},\"questions\":{\"charge\":{\"type\":\"choice\",\"instructions\":\"Should the owner plug in before a two-hour call?\",\"criteria\":{\"yes\":\"Plug in first\",\"no\":\"Fine as it is\"}}},\"actions\":[{\"id\":\"plug_in\",\"when\":{\"answer\":\"charge\",\"choice\":\"yes\",\"min_confidence\":0.8},\"do\":{\"type\":\"notice\",\"title\":\"Plug in before your call\",\"text\":\"Battery is at 12%.\"}}]}",
   "idempotent": false,
   "path": "/v1/judgment",
   "capability": "judgment",
   "returns": "model, answers {id: {type, choice+confidence | noul}}, usage, cost_usd, elapsed_ms, actions_schema (bridgit-actions/1), actions [{id, type, reason, ran, error?}] (only rules whose conditions held), skipped [{id, reason}]. When Jev fails you get an error and no action runs",
   "errors": [
    "invalid_parameter",
    "rate_limited",
    "upstream_failed",
    "team_unavailable",
    "payload_too_large"
   ],
   "requires": null
  },
  {
   "summary": "Call me back when ...: the phone reads the data you pick (location, motion, ...) on an interval, asks Jev (or Clef) your question about the last few readings, and calls you back when the answer matches",
   "method": "POST",
   "auth": true,
   "mcp_tool": "watches_start",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [
    {
     "name": "question",
     "description": "A yes/no question in words, e.g. \"Is the phone on a bicycle?\". You're called back when Jev's probability of yes reaches min_probability. Send this or questions",
     "type": "string",
     "required": false
    },
    {
     "name": "min_probability",
     "description": "With question: how likely yes must be, 0-1 (default 0.8)",
     "type": "number",
     "required": false
    },
    {
     "name": "questions",
     "description": "Instead of question: 1-10 typed questions, {id: {type: choice|noul, instructions, criteria?}}, as https://docs.typesafe.ai/primitives",
     "type": "object",
     "required": false
    },
    {
     "name": "callback_when",
     "description": "With questions: the answer that calls you back, 1-5 conditions that must all hold: {answer, min?, max?} on a noul probability, or {answer, choice, min_confidence?} (default 0.8) on a choice. Jev's confidence is not the probability: 0.67 likely is about 0.34 confident",
     "type": "object|array",
     "required": false
    },
    {
     "name": "data",
     "description": "What to read each time (default [\"location\", \"motion\"]): location (with speed), motion (2 s accelerometer burst), steps, light, pressure, proximity, battery, device (screen, lock, headphones, network). location needs the location permission, the sensors ones the sensors permission",
     "type": "array",
     "required": false
    },
    {
     "name": "interval_s",
     "description": "Seconds between readings, 30-3600 (default 60). A watch keeps the phone awake and reads location each time, so pick the longest interval and expiry that work. Phone-wide limit: 240 Jev checks an hour (50 on an install the Bridgit team couldn't attest). A still, unplugged phone may pause checks until it wakes",
     "type": "integer",
     "required": false
    },
    {
     "name": "window",
     "description": "How many recent readings Jev sees, 1-20 (default 5)",
     "type": "integer",
     "required": false
    },
    {
     "name": "min_readings",
     "description": "Readings to collect before the first question (default 3, at most window)",
     "type": "integer",
     "required": false
    },
    {
     "name": "expires_in_s",
     "description": "Stop after this long, up to 86400 (default 7200)",
     "type": "integer",
     "required": false
    },
    {
     "name": "repeat",
     "description": "false (default): one callback, then the watch ends. true: keep watching, at most one callback per cooldown_s",
     "type": "boolean",
     "required": false
    },
    {
     "name": "cooldown_s",
     "description": "With repeat: seconds between callbacks (default 600)",
     "type": "integer",
     "required": false
    },
    {
     "name": "callback_url",
     "description": "Where to POST the result, as well as your registered callback and /v1/events. Same rules as callbacks: a tailnet address, or https on the internet if the owner allows internet callbacks. Signed with the callback_secret in the reply",
     "type": "string",
     "required": false
    },
    {
     "name": "context",
     "description": "Anything Jev should know, up to 2000 characters (\"the owner commutes by bike\")",
     "type": "string",
     "required": false
    },
    {
     "name": "label",
     "description": "A short name for logs and lists (default: the question)",
     "type": "string",
     "required": false
    },
    {
     "name": "model",
     "description": "Decision model: jev (default, TypeSafe; best on reasoning about text) or clef (Cloudflare Clef-omni). Both share the phone's hourly limit",
     "type": "string",
     "required": false,
     "enum": [
      "jev",
      "clef"
     ]
    }
   ],
   "example": "POST /v1/watches {\"question\":\"Is the phone on a bicycle?\",\"data\":[\"location\",\"motion\"],\"interval_s\":60,\"window\":5,\"callback_url\":\"http://my-mac.your-tailnet.ts.net:8080/bridgit\"}",
   "idempotent": false,
   "path": "/v1/watches",
   "capability": "watches",
   "returns": "201: watch_id, label, created_at, expires_at, spec, callback_secret (only with callback_url; shown once), first_check_in_s, delivery. Each match is a watch.matched event {watch_id, label, question, answers, callback_when, model, checked_at, match, state (the readings Jev saw)}; the end is watch.ended {watch_id, reason: matched|expired|deleted|access_revoked|budget|rejected|failing, message, checks, matches}",
   "errors": [
    "invalid_parameter",
    "agent_not_allowed",
    "capability_disabled",
    "too_many_open",
    "rate_limited",
    "team_unavailable"
   ],
   "requires": null
  },
  {
   "summary": "Your running watches, with their last check",
   "method": "GET",
   "auth": true,
   "mcp_tool": "watches_list",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/watches",
   "idempotent": false,
   "path": "/v1/watches",
   "capability": "watches",
   "returns": "watches [{watch_id, label, created_at, expires_at, interval_s, data, checks, matches, last_match_at, readings_held, last_check}]",
   "errors": [],
   "requires": null
  },
  {
   "summary": "One running watch: its spec, the last 10 checks with Jev's answers, and the last state sent",
   "method": "GET",
   "auth": true,
   "mcp_tool": "watches_get",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/watches/w0123456789ab",
   "idempotent": false,
   "path": "/v1/watches/{id}",
   "capability": "watches",
   "returns": "watch_id, label, ..., spec, recent_checks [{at, readings, answers, matched, model} or {at, error, message}], last_state",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Stop a watch (you get a watch.ended event with reason deleted)",
   "method": "DELETE",
   "auth": true,
   "mcp_tool": "watches_stop",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "DELETE /v1/watches/w0123456789ab",
   "idempotent": false,
   "path": "/v1/watches/{id}",
   "capability": "watches",
   "returns": "deleted: the watch_id",
   "errors": ["not_found"],
   "requires": null
  },
  {
   "summary": "Every call this phone made to a server off the device: destination, purpose, which agent caused it, the data categories sent, bytes, status, time, and a redacted preview of what was sent and received",
   "method": "GET",
   "auth": true,
   "mcp_tool": "outbound_list",
   "query": [
    {
     "name": "since",
     "description": "Outbound id (exclusive) or ISO time",
     "type": "string",
     "required": false
    },
    {
     "name": "until",
     "description": "Outbound id or ISO time",
     "type": "string",
     "required": false
    },
    {
     "name": "destination",
     "description": "Text in the host or service name, e.g. met.no, jev, relay",
     "type": "string",
     "required": false
    },
    {
     "name": "agent",
     "description": "Comma list of agent names or ids; me; phone or owner for calls nobody's agent caused",
     "type": "string",
     "required": false
    },
    {
     "name": "category",
     "description": "Comma list of data categories, any of them, e.g. location_1km,image,install_id",
     "type": "string",
     "required": false
    },
    {
     "name": "method",
     "description": "GET, POST, SEND (relay frames)\u2026",
     "type": "string",
     "required": false
    },
    {
     "name": "result",
     "description": "ok or error",
     "type": "string",
     "required": false,
     "enum": [
      "ok",
      "error"
     ]
    },
    {
     "name": "q",
     "description": "Text in the purpose, path or sent preview",
     "type": "string",
     "required": false
    },
    {
     "name": "period",
     "description": "today, 24h, 7d or 30d (since overrides it)",
     "type": "string",
     "required": false,
     "enum": [
      "today",
      "24h",
      "7d",
      "30d"
     ]
    },
    {
     "name": "limit",
     "description": "1-500, default 100",
     "type": "integer",
     "required": false
    },
    {
     "name": "order",
     "description": "desc (default) or asc",
     "type": "string",
     "required": false,
     "enum": [
      "desc",
      "asc"
     ]
    },
    {
     "name": "cursor",
     "description": "next_cursor from the previous page",
     "type": "string",
     "required": false
    },
    {
     "name": "bodies",
     "description": "false leaves out the sent/received previews (default true)",
     "type": "boolean",
     "required": false
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/outbound?destination=met.no&limit=20",
   "idempotent": false,
   "path": "/v1/outbound",
   "capability": "outbound",
   "returns": "entries[id, at, destination{host, service}, method, path, purpose, agent, agent_id, categories[], bytes_sent, bytes_received, status, ok, error, duration_ms, query, headers (secrets redacted), sent_sha256, sent{type, preview}, received{type, preview}], next_cursor, latest_id, recording{on, changed_at, changed_by, note}, scope{sees: own|all}, coverage{recorded[], not_recorded[]}",
   "errors": ["invalid_parameter"],
   "requires": null
  },
  {
   "summary": "Analysis of what left the phone, in one call: a plain-language headline, totals, trends by hour or day, the biggest destinations, agents and calls, destinations first seen in the window, failures, and for each destination the data categories, purposes and agents it received",
   "method": "GET",
   "auth": true,
   "mcp_tool": "outbound_summary",
   "query": [
    {
     "name": "since",
     "description": "Outbound id (exclusive) or ISO time",
     "type": "string",
     "required": false
    },
    {
     "name": "until",
     "description": "Outbound id or ISO time",
     "type": "string",
     "required": false
    },
    {
     "name": "destination",
     "description": "Text in the host or service name, e.g. met.no, jev, relay",
     "type": "string",
     "required": false
    },
    {
     "name": "agent",
     "description": "Comma list of agent names or ids; me; phone or owner for calls nobody's agent caused",
     "type": "string",
     "required": false
    },
    {
     "name": "category",
     "description": "Comma list of data categories, any of them, e.g. location_1km,image,install_id",
     "type": "string",
     "required": false
    },
    {
     "name": "method",
     "description": "GET, POST, SEND (relay frames)\u2026",
     "type": "string",
     "required": false
    },
    {
     "name": "result",
     "description": "ok or error",
     "type": "string",
     "required": false,
     "enum": [
      "ok",
      "error"
     ]
    },
    {
     "name": "q",
     "description": "Text in the purpose, path or sent preview",
     "type": "string",
     "required": false
    },
    {
     "name": "period",
     "description": "today, 24h, 7d or 30d (default 30d; since overrides it)",
     "type": "string",
     "required": false,
     "enum": [
      "today",
      "24h",
      "7d",
      "30d"
     ]
    },
    {
     "name": "bucket",
     "description": "Trend step: hour or day (default hour for windows up to 48 h)",
     "type": "string",
     "required": false,
     "enum": [
      "hour",
      "day"
     ]
    }
   ],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/outbound/summary?period=today",
   "idempotent": false,
   "path": "/v1/outbound/summary",
   "capability": "outbound",
   "returns": "headline (\"Since 00:00 today: 7 calls sent 4.9 KB to 3 destinations: ...\"), window{since, until, bucket}, totals{calls, failed, bytes_sent, bytes_received, categories[], destinations, agents}, by_destination[{service, host, calls, failed, bytes, categories[], purposes[], agents[], first_at, last_at, new}], by_category[{category, destinations[], agents[]}], by_agent[{agent, destinations[]}], by_purpose[], by_day[], by_time[], biggest{destinations[], agents[], calls[]}, new_destinations[{host, first_at, categories[]}], failures{count, by_destination[], by_error[], recent[]}, recording{on, changed_at, changed_by}, scope, coverage",
   "errors": ["invalid_parameter"],
   "requires": null
  },
  {
   "summary": "One outbound entry with its previews",
   "method": "GET",
   "auth": true,
   "mcp_tool": "outbound_entry",
   "query": [],
   "developer_only": false,
   "capture": false,
   "body": [],
   "example": "GET /v1/outbound/412",
   "idempotent": false,
   "path": "/v1/outbound/{id}",
   "capability": "outbound",
   "returns": "the entry",
   "errors": [
    "not_found",
    "invalid_parameter"
   ],
   "requires": null
  }
 ],
 "auth": "Authorization: Bearer <your key> on every call where auth is true",
 "api_revision": 39,
 "count": 116,
 "base_url": "http://<phone>:<port> (the phone's address from your setup prompt)"
}
