Real-time Streams

Ephemeral broadcasts with optional recording — for data that's useful in the moment, durable when you ask for it.

The problem

Some data deserves to live forever — invoices, articles, user profiles. Some doesn't — a "now playing" track, presence pings, typing indicators, sensor readings. Persisting every now-playing event to the chain forever wastes disk, bandwidth, and replication on every node, for data nobody will read in three weeks.

Real-time streams give you a separate lane: broadcast to live listeners with zero persistence, with an opt-in recording toggle when you want a permanent slice (a podcast episode, a session log, a particular DJ set).

Three ways to use it

1. Direct broadcast

One-shot publish to anyone listening on the route. No setup needed.

POST https://api.infiniteocean.io/broadcast/myapp/now-playing
X-Ocean-Key: <your-key>
Content-Type: application/json

{"track": "Whatever - Whoever", "ts": 1778050000}

Returns {"ok":true}. Body is forwarded verbatim to listeners. No persistence side effect: no file, no manifest, no chain entry, no S3.

2. Stream-type routes (auto-route /drop)

Mark a route as a stream once, and every POST /drop on it is auto-routed to the broadcast lane. Your existing client code keeps working — the platform just stops persisting.

POST /drop
{
  "route":   "_config/myapp/now-playing",
  "key":     "stream",
  "payload": { "type": "stream" }
}

After this, every POST /drop on myapp/now-playing returns {"ok":true,"stream":true,"recording":false} and writes nothing. Existing data on the route stays exactly as it was — chain entries from before the opt-in remain immutable.

To opt out, write a tombstone (payload: null) on the same _config/<route> @stream entity.

3. Recording toggle

While recording is active on a stream-type route, drops are also persisted to <route>/recordings/<key>/ as real durable drops with full chain entries. Use it for episode capture, session archives, anything you want to keep.

POST /record/start
{ "route": "myapp/now-playing", "key": "ep-42" }
// drops on myapp/now-playing now ALSO land in
//   myapp/now-playing/recordings/ep-42/

POST /record/stop
{ "route": "myapp/now-playing" }
// back to ephemeral

The recordings sub-route is a normal route — searchable, queryable, exportable. Each recording is its own self-contained archive.

Listening

SSE stream. Each broadcast appears as a data: event with the publish body verbatim. Keepalives every 30s. Disconnect cleanly to stop.

GET https://api.infiniteocean.io/broadcast/myapp/now-playing/listen
X-Ocean-Key: <your-key>
// → text/event-stream

From a browser:

const ev = new EventSource(
  "https://api.infiniteocean.io/broadcast/myapp/now-playing/listen?key=" + KEY
);
ev.onmessage = (e) => {
  const data = JSON.parse(e.data);
  console.log("now playing:", data);
};

From the CLI:

curl -N --no-buffer https://api.infiniteocean.io/broadcast/myapp/now-playing/listen \
  -H "X-Ocean-Key: <your-key>"

Cross-server fan-out

If you publish on one node, listeners on every node receive the broadcast. Forwarding happens automatically via peer-to-peer HTTP, with loop prevention via the X-Broadcast-Forwarded header. You don't need to think about which node a listener happens to be connected to.

Endpoints reference

MethodPathPurpose
POST/broadcast/:routePublish ephemeral broadcast
GET/broadcast/:route/listenSSE listener
POST/record/startStart recording on stream-type route
POST/record/stopStop recording

Caveats

No replay. Listeners only see events that arrive after they connect. If you need history, use the recording feature.
Best-effort delivery. A peer that's down when you broadcast misses that broadcast forever. By design, like network packets, not records.
Migrating an existing route to stream-type? If your client uses GET /stream/:route (the manifest-poll SSE), it will silently stop receiving events after the route is marked as stream-type — because nothing is added to the manifest. Switch listeners to GET /broadcast/:route/listen before flipping the _config @stream switch.

Where this fits in the platform

Stream-type routes don't replace /drop — they extend it. Three persistence modes now:

The trust chain is unchanged. Stream broadcasts don't add chain entries — they're a separate lane. Anything in the recordings sub-route is a normal chain-entered drop with full durability guarantees.