Events (SSE)
A persistent event stream that delivers real-time reservation changes to your application.
Endpoint
GET /api/v1/events X-Api-Key: YOUR_API_KEY
Connect once; the server pushes events as they happen. Uses the
Server-Sent Events
protocol — a plain HTTP response with Content-Type: text/event-stream.
Each event is tenant-scoped: you only receive events from your own tenant.
Browser — native EventSource
The browser's built-in EventSource API handles reconnection automatically.
Dashboard SSE connections are authenticated via cookie session and do not count against
your per-tier SSE connection limit.
const es = new EventSource("/api/v1/events");
es.addEventListener("RESERVED", (e) => {
const evt = JSON.parse(e.data);
console.log("Reserved:", evt.resource, "by", evt.requester);
});
es.addEventListener("UNRESERVED", (e) => {
const evt = JSON.parse(e.data);
console.log("Released:", evt.resource);
});
es.addEventListener("RESERVATION_EXPIRED", (e) => {
const evt = JSON.parse(e.data);
console.log("TTL expired:", evt.resource);
});
es.onerror = () => {
// Browser reconnects automatically — no action needed here
};
event: RESERVED). Use addEventListener("RESERVED", ...)
rather than onmessage to receive typed events.
Server-side — Node.js
import EventSource from "eventsource"; // npm i eventsource
const es = new EventSource("https://cooplocker.com/api/v1/events", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
es.addEventListener("RESERVED", (e) => {
const evt = JSON.parse(e.data);
console.log(evt);
});
Server-side — Python
import sseclient, requests
resp = requests.get(
"https://cooplocker.com/api/v1/events",
headers={"X-Api-Key": "YOUR_API_KEY"},
stream=True
)
for event in sseclient.SSEClient(resp).events():
if event.event == "RESERVED":
print("reserved:", event.data)
Keepalive
The server sends a : keepalive comment every 15 seconds to prevent
proxy and load-balancer timeouts from dropping idle connections.
These are invisible to your event handlers.
Event types
| Type | When emitted |
|---|---|
RESERVED | A resource was successfully reserved. |
UNRESERVED | A resource was released — either by an explicit unreserve call or by TTL expiry. |
RESERVATION_EXPIRED | A TTL reservation was swept by the server expiry loop. |
RESERVE_FAILED | A reserve attempt was rejected (e.g. ALREADY_HELD). |
UNRESERVE_FAILED | An unreserve attempt failed (e.g. invalid token). |
TRANSACTION_APPLIED | A multi-resource transaction completed successfully. |
TRANSACTION_ROLLED_BACK | A transaction was rolled back (ALL_OR_NONE conflict). |
RESET | All reservations for the tenant were cleared. |
TIER_LIMIT_REACHED | A rate or capacity limit was hit. Includes limitType and limit fields. |
Event payload shape
All events share this base shape. Fields that don't apply to a given event type are null.
{
"type": "RESERVED",
"tenantId": "...", // your tenant ID
"resource": "job-42", // resource name
"requester": "worker-1", // who made the call
"explanation": "ACQUIRED", // outcome detail
"resourceType":"UserDefined", // tag from your reserve call
"apiKeyLabel": "worker-prod", // key label, if set
"requestType": "RESERVE",
"timestamp": "2026-05-13T12:00:00Z",
// Transaction events only:
"method": "ALL_OR_NONE",
"changes": [
{"resource":"lock-1","requester":"svc-a","requestType":"RESERVE","explanation":"ACQUIRED"}
],
// Tier limit events only:
"limitType": "ConcurrentReservations",
"limit": 500
}
Connection limits
API key and JWT authenticated SSE connections count against your tier limit. Dashboard connections (cookie session) are excluded from the count.
| Tier | SSE connections (API key) |
|---|---|
| Free | 1 |
| Starter | 5 |
| Pro | 20 |
| Enterprise | Unlimited |
Exceeding the limit returns HTTP 429 with an upgradeRequired: true
body rather than opening a stream.
Reconnection
If the connection drops, reconnect and re-subscribe. There is no event replay —
events emitted while disconnected are not buffered. For durable event history,
poll GET /api/v1/Reservation/activeReservations on reconnect to
resync state before re-subscribing.