Blink Camera Skill
Control and monitor Blink cameras via blinkpy (>= 0.25.3): status, snapshots,
cloud motion clips, arm/disarm, and live view. Every command prints a single
JSON document to stdout.
Setup
1. Install dependencies
pip3 install -r ~/.claude/skills/blink-skill/requirements.txt
2. Authenticate (interactive — handles 2FA in one process)
python3 ~/.claude/skills/blink-skill/blink_skill.py setup YOUR_EMAIL YOUR_PASSWORD
If 2FA is enabled, the tool prints PIN sent -- enter it: to stderr and
waits for you to type the PIN on stdin in the same process, then finishes
login and saves the refresh token. It must be one process: the OAuth CSRF token
and PKCE verifier are held in memory on the login instance, so a separate step
cannot resume the flow.
Run it in a real terminal (so input() can read the PIN). After success,
credentials.json holds the hardware_id + refresh_token; all later runs
reuse the refresh token and never prompt.
verify (fallback only — flaky)
python3 ~/.claude/skills/blink-skill/blink_skill.py verify [PIN]
Prefer setup. verify re-drives login using the username/password saved in
credentials.json; because it triggers a fresh login it also triggers a
new PIN, so a PIN passed on the command line is usually stale. Let it prompt
interactively, or just use setup.
Commands
Inventory & status
blink_skill.py cameras # every camera, full status
blink_skill.py networks # sync modules + arm state
blink_skill.py status "Front" # one camera (partial match)
blink_skill.py status # all cameras, summary
Status fields (honest — no fabricated percentages):
battery— string"ok"/"low"(Blink does not expose a %).battery_voltage— volts (raw 100ths-of-a-volt converted).temperature_f,temperature_c,wifi_strength,sync_signal_strength.product_type,camera_type,wired(bool),serial,version,motion_enabled,last_record.armed— read from the sync module (network), not the camera.
Snapshot
blink_skill.py snapshot "Front" # CHEAP: cached image, no capture
blink_skill.py snapshot "Front" --refresh # FRESH capture (battery cost)
- Default = cached:
refresh(force=True)then writes the already-cached thumbnail. No new capture, no battery hit. --refresh= fresh capture:snap_picture()-> wait 5s ->refresh(force=True)-> save. On battery cams the result JSON includes"warn": "fresh capture uses battery".- Saved to
~/.claude/skills/blink-skill/snapshots/.
Cloud motion clips & video
blink_skill.py clips # cloud motion clips
blink_skill.py clips --camera "Front" --limit 10
blink_skill.py clips --since "2026/07/01 00:00:00"
blink_skill.py events # clips + live motion flags
blink_skill.py video "Front" # download last cached clip
clips/eventsuseget_videos_metadata(since, camera="all", stop=pages)for a real server-side clip list (stopis a page cap ~25 items/page;--limitis derived into pages and then slices the newest N).--sinceaccepts"YYYY/MM/DD HH:MM:SS"; default is the last refresh time.videowrites the last cached clip viacamera.video_to_file; if none is cached it returns{"status": "no_clip"}(useclipsfor cloud clips).
Arm / disarm (per network / sync module)
blink_skill.py arm # arm all networks
blink_skill.py arm --network "Home" # arm one network
blink_skill.py disarm
blink_skill.py disarm --network "Home"
Arming is a sync-module operation (sync.async_arm), not per-camera.
Live view
blink_skill.py liveview "Front Door" # wired cam, ~30s
blink_skill.py liveview "Front Door" --seconds 60
blink_skill.py liveview "Backyard" --force # battery cam override
--secondsdefault 30, hard-capped at 300.rtsps://result: returns the URL + anffplay_hint. Playable directly for a short server-side window; this command does not proxy it.immis://result (proprietary): spins a local TCP relay (init_livestream->start-> hold--seconds->stop) and returns alocal_relaytcp://127.0.0.1:PORT. Marked"fragile": true— it only serves while the process is alive; connect a player during the window.- Battery cams are refused without
--force. Live view drains the battery fast and battery cams share a rough ~98-minute lifetime live-view budget. With--forcethe output still carries a loudwarn.
Battery safety, in one line
Default snapshots are cached (free). Only snapshot --refresh and liveview
touch the radio/capture path; on battery cams both warn, and liveview refuses
without --force.
Output & files
- All stdout is a single JSON object. Interactive setup prompts and live-view relay notices go to stderr.
- Snapshots/videos:
~/.claude/skills/blink-skill/snapshots/. - Credentials:
~/.claude/skills/blink-skill/credentials.json(chmod 600). It contains the OAuthrefresh_token+hardware_idand the account username/password saved by blinkpy'slogin_attributesround-trip — treat the file as a secret.
Troubleshooting
- "Not authenticated" — run
setup. - "refresh token expired and 2FA is required again" — re-run
setup. - 2FA didn't work — you must complete
setupin an interactive terminal so the PIN is entered in the same process.verifywith a stale PIN will fail. - Camera not found — names are partial, case-insensitive; run
cameras. - immis live view failed — expected; that path is experimental/fragile.
#blink #cameras #security #smart-home