# Farsight quickstart for agents

Farsight turns camera images or an SRT live stream into answers to visual questions. A **chamber** is the area being watched. Each camera position is a **view**; one device can send one or more views. Each paired device gets its own key. Views are created when you send the first image with a new `view` name.

0. **Pair this device.** Check for `FARSIGHT_API_KEY` or `~/.config/farsight/api_key` first. If a key exists, skip to the check at the end of this step. `FARSIGHT_API_KEY` overrides the file; `FARSIGHT_KEY_FILE` can point to another file.

   How device auth works:

   - `POST /v1/device/start` needs no key. It returns a short `code` for a person and a secret `device_token` for the device. Show the operator only the `code` and `verification_uri`.
   - The operator signs in at `verification_uri` (`https://farsight.observer/device`) and enters the code. The dash is optional. This makes a new API key, owned by the operator and named after the device.
   - The device polls `POST /v1/device/token` with its `device_token` every five seconds. HTTP 202 means pending. HTTP 200 with `status: "approved"` carries the `api_key`. HTTP 410 means the code expired (after ten minutes); start again. HTTP 401 means the token is unknown. `status: "revoked"` means the operator already revoked that key.
   - The code only approves; only the device token can collect the key. Keep the device token and key out of prompts, repositories, and logs.
   - Each device has its own key, listed at `https://farsight.observer/keys`. Revoking it there stops that device immediately and leaves other devices working. At most five pending requests are allowed per network address (HTTP 409).

   Run this to pair. It prints the code for the operator, waits for approval, and saves the key as a single line in `~/.config/farsight/api_key` (directory mode `700`, file mode `600`):

   ```sh
   (
     umask 077
     mkdir -p ~/.config/farsight
     json() { python3 -c "import json,sys; print(json.load(sys.stdin).get('$1', ''))"; }
     START=$(curl -fsS -X POST https://farsight.observer/v1/device/start \
       -H 'Content-Type: application/json' -d '{"name":"my device"}') || exit 1
     TOKEN=$(printf %s "$START" | json device_token)
     echo "Ask the operator to enter $(printf %s "$START" | json code) at $(printf %s "$START" | json verification_uri)"
     while sleep 5; do
       RES=$(curl -sS -X POST https://farsight.observer/v1/device/token \
         -H 'Content-Type: application/json' -d "{\"device_token\":\"$TOKEN\"}")
       case $(printf %s "$RES" | json status) in
         pending) ;;
         approved) printf %s "$RES" | json api_key > ~/.config/farsight/api_key; echo Paired.; break ;;
         *) echo "Pairing failed: $(printf %s "$RES" | json status). Start again."; exit 1 ;;
       esac
     done
   )
   ```

   Check the key before setup:

   ```sh
   FARSIGHT_API_KEY="${FARSIGHT_API_KEY:-$(cat "${FARSIGHT_KEY_FILE:-$HOME/.config/farsight/api_key}")}"
   : "${FARSIGHT_API_KEY:?Pair this device first}"
   curl -fsS https://farsight.observer/v1/me \
     -H "Authorization: Bearer $FARSIGHT_API_KEY" >/dev/null
   ```

1. **Create a chamber** with `POST /v1/chambers`. Give it `name` and visual `questions` (`boolean` or `integer` only); optionally set `context` and `webhook_url`. Save the returned `id` as `CHAMBER_ID`.

   ```sh
   curl -fsS https://farsight.observer/v1/chambers \
     -H "Authorization: Bearer $FARSIGHT_API_KEY" \
     -H 'Content-Type: application/json' \
     -d '{"name":"Tank A","questions":[{"key":"fish_visible","prompt":"Is a fish visible?","type":"boolean"}]}'
   ```

2. **Send images as observations.** Use one stable `view` name per camera position, including when a device has several cameras. The first image creates that view. Save the returned observation `id`; poll its `status` and `answers` or use a chamber webhook.

   For a Linux or macOS camera, download the [Python observation script](https://farsight.observer/observe.py) and run it below. It uses FFmpeg and the paired key file. Edit `camera_input()` to select the camera; uncomment more named cameras and run `--all` to send each one. Use `--view side` for one named camera or `--image frame.jpg` to send a saved JPEG. Each invocation creates one new observation per selected camera; retrying a POST after an uncertain network failure can create another observation.

   ```sh
   curl -fsS https://farsight.observer/observe.py -o observe.py
   python3 observe.py --chamber CHAMBER_ID
   ```

   ```sh
   curl -fsS "https://farsight.observer/v1/chambers/$CHAMBER_ID/observations?view=top" \
     -H "Authorization: Bearer $FARSIGHT_API_KEY" \
     -H 'Content-Type: image/jpeg' --data-binary @frame.jpg
   curl -fsS "https://farsight.observer/v1/observations/$OBSERVATION_ID" \
     -H "Authorization: Bearer $FARSIGHT_API_KEY"
   ```

3. **Or set up a live stream.** Create one view per camera with `POST /v1/chambers/{id}/views`, then call `POST /v1/chambers/{id}/views/{view_id}/stream` to get its `srt_url`. A chamber can run several camera streams. Each view has its own `snapshot_minutes` interval (1–1440; default 120).

   Download the [Python stream script](https://farsight.observer/stream.py) and run it below. It uses FFmpeg, creates the named view if needed, and reuses that view's SRT URL. Edit `camera_input()` and use `--view side` to publish another camera in the same chamber.

   ```sh
   curl -fsS https://farsight.observer/stream.py -o stream.py
   python3 stream.py --chamber CHAMBER_ID
   ```

   ```sh
   VIEW_ID=$(curl -fsS -X POST "https://farsight.observer/v1/chambers/$CHAMBER_ID/views" \
     -H "Authorization: Bearer $FARSIGHT_API_KEY" -H 'Content-Type: application/json' \
     -d '{"name":"top"}' | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
   SRT_URL=$(curl -fsS -X POST "https://farsight.observer/v1/chambers/$CHAMBER_ID/views/$VIEW_ID/stream" \
     -H "Authorization: Bearer $FARSIGHT_API_KEY" | \
     python3 -c 'import json,sys; print(json.load(sys.stdin)["srt_url"])')
   ffmpeg -i INPUT -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 \
     -map 0:v:0 -map 1:a:0 -vf fps=10,setsar=1 \
     -c:v libx264 -preset ultrafast -tune zerolatency \
     -b:v 1000k -g 30 -keyint_min 30 -sc_threshold 0 \
     -pix_fmt yuv420p -c:a aac -b:a 96k -f mpegts "$SRT_URL"
   ```

   For a video file, add `-re` before `-i INPUT`. A live camera sets its own capture pace. The `fps=10` filter keeps the output steady when the camera delivers fewer frames than requested. A steady 10 fps is enough for monitoring; 30 fps gives smoother motion. The camera or encoder must give frames timestamps that advance by one second per second of real time, and it must keep up with that pace. During a long FFmpeg run, check that `speed=` stays at or above `1.0x`. If it stays lower, reduce the capture resolution, frame rate, or bitrate, or use a faster encoder. A slow publisher can make the player spin even while SRT says connected. Slowing playback would show slow motion instead of fixing the stream.

Full [agent and API reference](https://farsight.observer/docs.md) ([web view](https://farsight.observer/docs)); [llms.txt](https://farsight.observer/llms.txt); [API base](https://farsight.observer/v1).
The [OpenAPI schema](https://farsight.observer/openapi.json) describes the support routes.

If a stream fails, send a report from [Support](https://farsight.observer/support) or use `POST /v1/support` with `{ "title": "Stream fails", "message": "What happened", "logs": "FFmpeg output" }`. Remove the SRT URL and keys from logs first. Each of `message` and `logs` can hold 10,000 characters. Read the status and team replies with `GET /v1/support/{id}` and reply with `POST /v1/support/{id}/comments` and `{ "body": "..." }`.
