Docs / Site feeds

Site feeds

A feed is a long-lived exchange between one camera at one site and the platform. Video is cut into short segments, each carrying a sequence number, and delivered inside a single session. Sites lose connectivity constantly — a truck parks on the line, an LTE modem reboots — so the protocol is built around resuming rather than restarting.

Diagram of a site feed: camera, segments, session and backfill

Endpoint

GET  /api/v1/plants/feed/{plant}/{stream}
HEAD /api/v1/plants/feed/{plant}/{stream}

GET transfers a segment. HEAD acknowledges one and requests the next window without moving a body — on a metered LTE link the difference over a month is not academic. plant is the site identifier, stream the camera within it.

Opening a session

The session identifier travels in the session_id cookie and the current sequence number in csrftoken. A session lives 24 hours from the last exchange, so a healthy site opens one and keeps it for days.

curl -X POST https://<endpoint>/api/v1/plants/session \
  -H "Authorization: Bearer $NORDVIK_SITE_KEY" \
  -d "plant=gdansk-dc2" \
  -d "stream=gate-a" \
  -d "segment_seconds=4"

Response:

{
  "session": "sess_8f21ac",
  "plant": "gdansk-dc2",
  "stream": "gate-a",
  "segment_seconds": 4,
  "next_seq": 0,
  "expires_at": "2026-08-04T18:00:00Z"
}

Moving segments

Pull a segment:

curl -s "https://<endpoint>/api/v1/plants/feed/gdansk-dc2/gate-a?seq=4812" \
  -H "Authorization: Bearer $NORDVIK_SITE_KEY" \
  --cookie "session_id=sess_8f21ac; csrftoken=4812" \
  -o segment-4812.ts

Acknowledge it and request the next window:

curl -I "https://<endpoint>/api/v1/plants/feed/gdansk-dc2/gate-a?seq=4812" \
  -H "Authorization: Bearer $NORDVIK_SITE_KEY" \
  --cookie "session_id=sess_8f21ac; csrftoken=4812"

Backfill after an outage

# After the uplink comes back, ask what we actually have:
curl -s "https://<endpoint>/api/v1/plants/session/sess_8f21ac" \
  -H "Authorization: Bearer $NORDVIK_SITE_KEY"

# {"session":"sess_8f21ac","acked_seq":4811,"gap":[4780,4811]}
# Push the gap, then continue live. Recorders that restart from zero
# are the single most common integration mistake.

Limits

Segment length 2 to 10 seconds, 4 by default
Session lifetime 24 hours from the last exchange
Concurrent streams per site up to 32
Retention 30 / 180 / 365 days depending on plan
Backfill window up to 72 hours after the gap

Response codes

CodeMeaning
200 Segment returned. The body is the media payload.
400 No usable session: the session cookie is missing, the sequence number is malformed, or the request arrived outside an open session. A bare browser request to this endpoint lands here.
404 The session expired, or the plant and stream pair does not exist.
429 Too many concurrent segment requests for this site. Back off using the Retry-After header.

Opening this URL in a browser returns 400, not an error page: there is no session to attach the request to. That is expected and is not a sign the site is down — check service status instead.