Ephemeral broadcasts with optional recording — for data that's useful in the moment, durable when you ask for it.
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).
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.
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.
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.
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>"
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.
| Method | Path | Purpose |
|---|---|---|
| POST | /broadcast/:route | Publish ephemeral broadcast |
| GET | /broadcast/:route/listen | SSE listener |
| POST | /record/start | Start recording on stream-type route |
| POST | /record/stop | Stop recording |
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.
Stream-type routes don't replace /drop — they extend it. Three persistence modes now:
POST /drop persists every event to the chain forever (like always).POST /drop publishes to listeners, no persistence. Existing data untouched.recordings/<key>/ sub-route. Toggle on/off any time.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.