Integration: Catena tickets¶
This page is for the developer of a storefront or billing system — the place where you sell access. Instead of answering a callback on every viewer, you create the session ahead of time and hand the viewer a ticket. The streamer admits the ticket on its own: it checks the signature, expiry, and scope locally, with no call to your systems on the playback path.
The payoff: playback no longer depends on your storefront's speed or availability, streams start and switch instantly, and there is no per-viewer callback to keep scaled. Your systems are needed at the moment you sell access — not while the viewer watches.
Create a grant¶
When you sell access, call the management API to create a grant. Catena signs a ticket (a JWT) and returns it — the token is handed back only at creation; the registry keeps the grant, not the ticket.
Authenticate the call the way you authenticate any management API request — with the admin login and password. So your storefront never has to hold full admin credentials, you can instead configure a dedicated issuer key in central — a bearer token whose only power is to issue and manage grants — and send it as Authorization: Bearer <issuer-key>.
POST /play-sessions/grants
{
"streams": ["news", "arena"],
"user_id": "subscriber-4821",
"ttl_secs": 14400,
"max_concurrent": 2,
"bind_ip": "203.0.113.0/24",
"note": "family plan"
}
| Field | Meaning |
|---|---|
streams |
Which streams the ticket may play — its scope |
user_id |
The account the ticket belongs to; groups the viewer's screens |
ttl_secs |
Ticket lifetime; default 4 hours. exp is a hard end — a session cannot outlive it (players do not refresh the token) |
max_concurrent |
Simultaneous screens; default 1, null for unlimited |
bind_ip |
Optional IP or CIDR the ticket is useless outside of |
note |
Optional free-text label |
The reply carries the grant and the token. Hand that token to the player, which presents it on the play URL as ?token=<jwt> (or in an Authorization: Bearer header). Nothing else is required of the viewer — the streamer takes it from there.
Access modes¶
The access field in the POST /play-sessions/grants body limits what the ticket plays: live, archive, or part of the archive. Without the field the ticket opens the streams in full.
| Allowed | access |
|---|---|
| Live only | {"live": true, "archive": false} |
| Archive only, all recordings | {"live": false, "archive": true} |
| Live and the last 24 hours of archive | {"live": true, "archive": {"depth_secs": 86400}} |
| One archive range only | {"live": false, "archive": {"from": 1793000000, "to": 1793003600}} |
live opens or closes live playback. archive takes true (all recordings), false (archive closed), a depth depth_secs — how many seconds of archive back from the current moment — or a range from–to in unix seconds. The end of the range may lie in the future: recordings that fall into the range become available as they are written.
The streamer enforces the mode from the ticket itself, without calling central or your systems, on every request:
- a request outside the mode gets 403; the rest of the session keeps working;
- archive playlists, recording windows, export and RTSP archive reads are clipped to the allowed part; RTSP archive playback ends at the end of the range;
- with live closed, live playlists and live protocols are refused;
- analytics episodes are visible only inside the allowed archive;
- VOD and the
inforequest are not limited by the mode.
An invalid mode is rejected with 422 and the path to the field: an empty mode ("live": false and "archive": false) — path access; depth together with a range, from not less than to, zero depth or an incomplete range — path access.archive.
The mode is set at issue and never changes: PATCH with an access field is rejected with 422. For a different mode, issue a new grant.
Streamers of the current version enforce the mode. A streamer of the previous version does not know the access field and admits the ticket to the whole stream, so upgrade the streamers first, then start issuing grants with a mode. Mode fields a streamer does not know are skipped; the known ones apply.
A grant with a mode is issued by your storefront or middleware through the API, or by an operator by hand: on the stream page of the console, the "Issue a play ticket" form has an "Access" field with "Everything", "Live only", "Archive only", "Live and archive", and for the archive "All recordings", "Last N hours" or "Range"; a server refusal is shown on the field. The Catena storefront does not yet issue grants with a mode to subscribers.
Screens¶
max_concurrent limits how many screens actively consume the ticket at once — a household with two TVs, say. Go over the limit and the oldest screen is dropped; the newest wins. You can change the limit later without reissuing the ticket, because it lives in the registry, not in the signed token:
PATCH /play-sessions/grants/{jti}
{ "max_concurrent": 3 }
Revoke¶
Cancel a grant and every streamer drops its viewers within seconds and refuses the ticket from then on — the cancellation travels to the streamers over a revocation feed, not by waiting for the ticket to expire. There is no reauthorization for ticket sessions; revocation is the single stream that ends them early.
Roaming¶
A viewer whose address changes — a phone leaving Wi-Fi for mobile — is admitted afresh on the new address, offline, while the old session dies by idle timeout. It counts as one journey, not a new charge, grouped by the grant. If you set bind_ip, roaming stays within that range.
List and status¶
GET /play-sessions/grants lists grants and their status — issued (handed out, not yet played), played (used at least once), revoked, expired — filterable by user_id and status. This is the record of what you sold and whether it was used.
Migrate at your own pace¶
Tickets and an existing backend run side by side — tickets for some viewers, the callback for others — so you can move audience by audience. The end state is the switch opaque_tokens: false on the auth policy: once set, any non-ticket token is refused outright, without consulting lists or a backend, and the callback path is closed for good.
Next steps¶
- Protect playback — the operator's view of the same policy.
- Session lifecycle — how a ticket session opens, closes, and survives a cluster hiccup.
- flussonic authorization — the callback path you are migrating from.