Skip to content

Receiving

Read keys, listing, the live stream, reconnecting, and dismissal.

Access: Keys with the events:read scope, issued by Albastar Corp.

A reader (an app such as a dashboard) reads events with a read key: a list for history, a server-sent event stream for new events.

Read keys

Albastar Corp. issues each read key with a name, such as dashboard, and a grant: every source, or a list of sources. Every response and stream frame holds events of granted sources only. Store the key as ALBASTAR_EVENTS_READ_KEY.

A publish key on a read route fails with 403 scope_missing. Dismissal state belongs to the read key: two readers with the same key share dismissals; two keys do not.

Check a key

GET /v1/events/info returns the key's kind, name and granted sources. The route accepts a read key or a publish key. Call the route once when a reader adds a connection.

Shell
curl https://api.albastov.com/v1/events/info \
  -H "Authorization: Bearer $ALBASTAR_EVENTS_READ_KEY"
Response
{
  "protocol": "ambient/1",
  "name": "Albastar API",
  "key": "reader",
  "reader": "dashboard",
  "sources": ["stripe", "vercel"],
  "features": ["batch", "search"],
  "docs": "https://api.albastov.com/v1/events/docs"
}

List events

GET /v1/events lists events newest first, 100 by default and at most 500 a page. Filter by source, type, severity_min and text (q); sticky=open lists the sticky events this key has not dismissed.

curl

curl "https://api.albastov.com/v1/events?limit=5&severity_min=warn" \
  -H "Authorization: Bearer $ALBASTAR_EVENTS_READ_KEY"

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events?limit=5&severity_min=warn",
    headers={
        "User-Agent": "my-app/1.0",
        "Authorization": f"Bearer {os.environ['ALBASTAR_EVENTS_READ_KEY']}",
    },
)
for event in json.load(urllib.request.urlopen(req))["events"]:
    print(event["received_at"], event["source"], event["text"])

JavaScript

const res = await fetch(
  "https://api.albastov.com/v1/events?limit=5&severity_min=warn",
  {
    headers: {
      Authorization: `Bearer ${process.env.ALBASTAR_EVENTS_READ_KEY}`,
    },
  },
);
for (const event of (await res.json()).events) {
  console.log(event.received_at, event.source, event.text);
}

Page back with before=<next_before> until next_before is null. Catch up with after=<cursor>: events after the cursor, oldest first. latest_cursor is the newest cursor among the granted sources; pass the value as after or as the stream's Last-Event-ID to resume. Response fields: List events.

Stream events

GET /v1/events/stream holds a text/event-stream open and sends each new event of the granted sources as an event frame whose id is the event's cursor. A state frame reports a dismissal by the same key, a gap frame reports events the catch-up left out, and a : keepalive comment arrives every 20 s.

Shell
curl -N https://api.albastov.com/v1/events/stream \
  -H "Authorization: Bearer $ALBASTAR_EVENTS_READ_KEY"
Frames, data shortened
retry: 3000
: connected

id: 1752
event: event
data: {"id":"evt_01M6GDQMZRD7F5H9K3M6P8R1T4","cursor":"1752",…}

event: state
data: {"id":"evt_01M6GDQM…","dismissed_at":"2026-11-03T10:05:00Z"}

: keepalive

Without a cursor, the stream starts at new events. With Last-Event-ID: <cursor> or ?after=<cursor>, the stream first sends the events after the cursor (at most 1,000), then this key's dismissals since the cursor, then new events. Every frame: Stream events.

Reconnect

  1. On start, call GET /v1/events/info, then GET /v1/events?sticky=open for open sticky events, then GET /v1/events?limit=1 for latest_cursor.
  2. Open the stream with Last-Event-ID: <latest_cursor>.
  3. Store the id of every event frame.
  4. Reconnect when 60 s pass without a byte, or when the connection closes. Wait 1 s, then double the wait up to 30 s, with jitter; reset the wait after a successful connection.
  5. Resume with Last-Event-ID: <last stored id>. On a gap frame, fetch the skipped events with GET /v1/events?after=<after>&limit=500, repeating with the last cursor of each page as after until the page reaches cursor resume.
  6. On 429 too_many_streams, close an unused stream of the same key, then retry.

Dismiss events

POST /v1/events/{id}/dismiss sets this key's dismissal of an event; DELETE on the same path clears the dismissal. Both are idempotent: a second POST keeps the first time. The response is the event with dismissed_at, and every open stream of the same key receives a state frame.

Shell
curl -X POST \
  https://api.albastov.com/v1/events/evt_01M6GDQMZRD7F5H9K3M6P8R1T4/dismiss \
  -H "Authorization: Bearer $ALBASTAR_EVENTS_READ_KEY"

Conventions

  • Cursors are opaque decimal strings, strictly increasing in store order.
  • Times are RFC 3339 UTC. Optional fields are null.
  • Errors are JSON with code and message.

Reference

  • Events reference: query parameters, frames, responses and error codes for every read route.