Farsight
Farsight watches scientific observation chambers (tanks, cages, terraria, rearing boxes with moths, fish, rats, and so on). You send images (or an SRT stream). Farsight analyses each image asynchronously and answers the chamber's questions as typed data: booleans and integers only.
- Base URL: https://farsight.observer/v1
- Auth:
Authorization: Bearer <api key>(keys start withfs_). Create keys in the dashboard at https://farsight.observer/keys. - Errors:
{"error": {"code": "...", "message": "...", "field": "..."}}with HTTP 401, 404, 422, or 503.
Concepts
- Chamber: one physical viewing area. It has questions, views, an optional webhook, and an optional SRT stream.
- Question:
{key, prompt, type, min?, max?}.keyis snake_case.typeisbooleanorinteger. No floats, no strings. An answer is null when the image cannot decide it. - View: one camera position in a chamber. Pass
view(id or name) with an image to pin it; a new name creates the view. Omit it and Farsight matches the image to a known view, or creates one. - Observation: one image plus its analysis.
statusisqueued,processing,complete, orfailed.
Quick start
KEY=fs_your_key
# 1. Create a chamber with questions
curl -s https://farsight.observer/v1/chambers -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{
"name": "Moth box A",
"snapshot_minutes": 5,
"questions": [
{"key": "cocooning_stage", "prompt": "Is any caterpillar spinning or inside a cocoon?", "type": "boolean"},
{"key": "moth_count", "prompt": "How many adult moths are visible?", "type": "integer", "min": 0}
],
"webhook_url": "https://example.com/farsight"
}'
# 2. Send an image (raw bytes; view is optional)
curl -s "https://farsight.observer/v1/chambers/CHAMBER_ID/observations?view=top" -H "Authorization: Bearer $KEY" \
-H "Content-Type: image/jpeg" --data-binary @frame.jpg
# 3. Read the result (or wait for the webhook)
curl -s https://farsight.observer/v1/observations/OBSERVATION_ID -H "Authorization: Bearer $KEY"
Endpoints
Chambers
GET /v1/chamberslist.POST /v1/chambersbody{name, snapshot_minutes?, questions?, webhook_url?}.snapshot_minutesis 1 to 1440, default 5.GET /v1/chambers/{id}full chamber with questions, views, webhook, stream.PATCH /v1/chambers/{id}any of the create fields.questionsreplaces the whole list.DELETE /v1/chambers/{id}deletes the chamber, its images, and its stream.
Questions
GET /v1/chambers/{id}/questionsPOST /v1/chambers/{id}/questionsbody{key, prompt, type, min?, max?}. Same key updates it.DELETE /v1/chambers/{id}/questions/{key}
Observations
POST /v1/chambers/{id}/observationsreturns 202 and the queued observation. Send the image one of three ways:
- raw bytes withContent-Type: image/jpeg|png|webp|gif, options in the query string:?view=top&captured_at=2026-01-01T00:00:00Z
-multipart/form-datawith fieldimage, plus optionalviewandcaptured_atfields
- JSON{"image_base64": "...", "view": "top"}or{"image_url": "https://..."}
Max 10 MB and 60 images per chamber per minute (422, fieldrate).captured_atis ISO 8601 or unix seconds/ms; default is receipt time.GET /v1/chambers/{id}/observations?limit=20&cursor=...&view=VIEW_IDnewest first; follownext_cursor.GET /v1/observations/{id}GET /v1/observations/{id}/image
Observation shape:
{
"id": "ob_...", "chamber_id": "ch_...", "status": "complete", "source": "api",
"view": {"id": "vw_...", "name": "top"},
"answers": {"cocooning_stage": true, "moth_count": 0},
"note": "Two caterpillars on the left branch; one partly wrapped in silk.",
"error": null,
"image_url": "https://farsight.observer/v1/observations/ob_.../image",
"captured_at": "...", "created_at": "...", "completed_at": "..."
}
Analysis usually finishes in 3 to 15 seconds. Poll GET /v1/observations/{id} about every 2 seconds, or use the webhook.
Views
GET /v1/chambers/{id}/viewsPATCH /v1/chambers/{id}/views/{view_id}body{name}DELETE /v1/chambers/{id}/views/{view_id}GET /v1/views/{view_id}/imagethe reference frame used for matching.
Webhook
- Set
webhook_urlon the chamber (https). Events:observation.completed,observation.failed,ping. - Body:
{"type": "observation.completed", "created_at": "...", "data": <observation>}. - Header
Farsight-Signature: t=<unix>,v1=<hex>where hex is HMAC-SHA256 of<t>.<raw body>with the chamber'swebhook.secret. Reject if t is more than 5 minutes old. - Retries with backoff for up to 6 attempts on a non-2xx response.
POST /v1/chambers/{id}/webhook/testsends a ping.POST /v1/chambers/{id}/webhook/rotatereturns a new secret.
Stream (SRT)
POST /v1/chambers/{id}/streamenables ingest and returns{srt_url, state, snapshot_minutes}.DELETEdisables it.- Publish with any SRT caller, for example:
ffmpeg -re -i INPUT -c:v libx264 -preset veryfast -b:v 2500k -g 60 -c:a aac -f mpegts "SRT_URL" - Every
snapshot_minutes(minimum 1) Farsight takes a frame from the live stream and analyses it as an observation withsource: "stream". - The last hour of video is kept as 60 second MP4 clips:
GET /v1/chambers/{id}/clips, thenGET /v1/clips/{clip_id}.
Keys
GET /v1/keys,POST /v1/keysbody{name}returns the full key once,DELETE /v1/keys/{id}revokes.
Tips for agents
- Write each question prompt as a plain visual check a person could answer from one frame.
- Use integer questions with
min/maxfor counts and levels (for example 0 to 5). Answers are clamped to the range. - Keep one camera per view name, and send
viewwhen you know it; matching is best effort.