Skip to content

Events

Citadel Events stores short notices from apps (a deploy finished, a sale, a site down) and streams them to readers such as Pager. The API implements the Ambient protocol, version ambient/1.

TermDefinition
SourceA named stream of events: github, narkis-production. Slug [a-z0-9-]{2,48}, plus a name, icon and color
EventOne notice: type, severity, one line of text, optional detail, subject, url, display, data
Publish keyabk_ key bound to one source; publishes into that source only
Read keyabk_ key granted every source or a list of sources; lists, streams and dismisses
WebhookA URL that turns another app's HTTP requests into events of one source, by a mapping

Base URL: https://citadel.albastov.com/v1/events. The owner creates sources and mints keys in the admin panel.

Quick start

Publish one event with a publish key:

Shell
curl https://citadel.albastov.com/v1/events \
  -H "Authorization: Bearer $CITADEL_EVENTS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"backup.done","text":"Backup **done** · 412 MB","idempotency_key":"backup-2026-10-31"}'
Response 201
{"id":"evt_01JBX7Q2W8V5K3M9N4P6R2T8YC","cursor":"1734","source":"backups","source_name":"Backups","source_color":null,"source_icon":null,"type":"backup.done","severity":"info","text":"Backup **done** · 412 MB","detail":null,"subject":null,"url":null,"occurred_at":"2026-10-31T10:02:03Z","received_at":"2026-10-31T10:02:03Z","display":null,"data":null,"dismissed_at":null}

Read the newest events with a read key:

Shell
curl "https://citadel.albastov.com/v1/events?limit=5" -H "Authorization: Bearer $CITADEL_EVENTS_READ_KEY"

The examples are not run by the example checker: the checker holds an API key, not an events key.

Reference

Ways in and out

DirectionRouteCredentialPage
In: publishPOST /v1/events, POST /v1/events/batchPublish keySources and publishing
In: webhookPOST /v1/events/hooks/in/{token}Token in the URL, optional signatureWebhooks
Out: listGET /v1/eventsRead keyReceiving events
Out: streamGET /v1/events/streamRead keyReceiving events

Every event enters one store and reaches every stream whose grant includes its source. Publishing and webhooks apply the same field validation and the same idempotency_key dedupe; webhooks also drop a repeated body within 10 minutes (Duplicates).

Events

FieldValue
type[a-z0-9._-]{1,48}, dotted nouns: deploy.failed, pr.merged
severitydebug, info, notice, warn, error, critical; default info
textOne line, at most 300 characters; inline **bold**, *italic*, `code`
detailPlain text, at most 1000 characters, newlines allowed
subjectOne line, at most 80 characters
urlAbsolute https:// URL
occurred_atRFC 3339; default the time of receipt; clamped to 7 days either side of receipt
displayPresentation hints for readers: lifetime, layout, effect, target, period, color
dataJSON object, at most 8 KB, stored as sent

Citadel adds id, cursor, source, source_name, source_color, source_icon, received_at and, for a reader, dismissed_at.

SeverityUse for
debugWiring tests
infoCompleted work: deploy done, PR merged, backup done, sale
noticeItems to read: PR opened, new signup, configuration change
warnDegraded service: retries, slow responses, disk at 85%
errorFailures that need a person: deploy failed, site down
criticalMoney, data or security at risk now

Lifetime and sticky

display.lifetime sets how long a reader shows an event: whole seconds 1..86400, or "sticky". A sticky event stays on screen until a reader dismisses it. Use "sticky" for states that last until someone acts: a site down, a failed deploy. Without lifetime, the reader's default applies (Pager: 60 s). Dismissal is per read key: one reader dismissing an event leaves the event open for every other reader.

Citadel's own sources

SourceNameOrigin
githubGitHubWebhook (built-in)
coolifyCoolifyWebhook (built-in)
narkisStripeWebhook (built-in): Narkis sales
hostkeyHOSTKEYWebhook (built-in)
uptimeUptimeCitadel's uptime checker, plus remote checkers through a webhook (built-in)
citadelCitadelCitadel's own 500 responses, panics and failed jobs
generationsGenerationsImages, videos and songs made by members
membersMembersSign-ins, access requests, gallery posts
crashesBrowser crashesNew and returning browser crash signatures from Albastar sites

The built-in webhooks are ordinary webhooks with seeded mappings; see Built-in webhooks. The uptime checker, citadel, generations, members and crashes are written by Citadel itself, not received as webhooks, and take no webhook and no publish key.

Do not publish deploy, CI or sale events that GitHub, Coolify or Stripe already report.

Pager

Pager is the macOS reader for ambient/1 hubs. Pager connects with a read key, calls GET /v1/events/info, reads open sticky events with GET /v1/events?sticky=open, and holds GET /v1/events/stream open. Pager draws each event as one line beside its source's name, icon and color, applies display hints, and dismisses sticky events with POST /v1/events/{id}/dismiss. See Receiving events.

Agent guide

GET /v1/events/docs returns this section (the four Events pages) as one text/markdown document, without authentication. A publish key's brief links to it.

Limits

LimitValue
Publish body16 KB; batch 256 KB
Publish rate, per publish key120 requests a minute, bursts of 30; 300 events a minute
Webhook body2 MiB
Webhook rate, per source120 deliveries a minute, bursts of 30
Webhook noise budget, per source5 events a minute shown one by one; the rest as one summary event
idempotency_key window7 days, per source
Streams, per read key8 open
History90 days per source by default

Changelog

  • 2026-10-31: Every webhook, GitHub, Coolify, Stripe, HOSTKEY and remote uptime included, runs through one receiver with a mapping stored as data. Signatures can be required per source; refused requests are counted. New webhooks start pending and publish after review. A delivery without an idempotency key or delivery header dedupes on its body for 10 minutes.
  • 2026-10-30: Webhooks for any source: POST /v1/events/hooks/in/{token} with a mapping.
  • 2026-10-15: ambient/1: publish, batch, list, stream, dismiss, sources, info.