Skip to content

Events

Publish events, receive webhooks, and list, stream and dismiss events.

The Events routes publish events into a source with a publish key, turn webhooks into events, and list, stream and dismiss events with a read key.

RouteCredential
POST /v1/events, POST /v1/events/batch, PATCH /v1/events/sources/selfPublish key
POST /v1/events/hooks/in/{token}Token in the URL, optional signature
GET /v1/events, GET /v1/events/sources, GET /v1/events/stream, POST/DELETE /v1/events/{id}/dismissRead key
GET /v1/events/infoPublish key or read key
GET /v1/events/docsNone

Publish keys and read keys are abk_ keys minted on the Events page of the albastov.com admin panel. An error is JSON with code, message and, for a field error, meta.field:

Error
{"ok": false, "error": "invalid-field", "code": "invalid_field", "message": "text is 312 characters; at most 300", "meta": {"field": "text"}}

Event object

NameTypeDescription
idstringEvent id, evt_ and 26 characters
cursorstringPosition in the store, a decimal string, strictly increasing
sourcestringSource slug
source_namestringSource name
source_colorstring or nullSource colour, #rgb, #rrggbb or #rrggbbaa
source_iconstring or nullSimple Icons slug or SF Symbol name
typestringEvent type
severitystringdebug, info, notice, warn, error or critical
textstringOne line
detailstring or nullPlain text, newlines allowed
subjectstring or nullOne line
urlstring or nullhttps:// URL
occurred_atstringRFC 3339 UTC
received_atstringRFC 3339 UTC
displayobject or nullPresentation hints as published
dataobject or nullJSON object as published
dismissed_atstring or nullWhen the calling read key dismissed the event; null otherwise and on publish responses

Publish an event

POST /v1/events stores one event in the publish key's source and returns the stored event.

Headers

NameDescription
AuthorizationBearer <publish key>
Content-Typeapplication/json
Retry-AfterResponse header on 429: seconds to wait

Parameters

NameTypeDescription
type requiredstring[a-z0-9._-]{1,48}, a dotted noun: deploy.succeeded, pr.merged, site.down
text requiredstringOne line, at most 300 characters; inline **bold**, *italic*, `code`
severitystringdebug, info, notice, warn, error or critical; default info
detailstringPlain text, at most 1000 characters, newlines and tabs allowed
subjectstringOne line, at most 80 characters
urlstringAbsolute https:// URL, at most 2048 characters
occurred_atstringRFC 3339; default the time of receipt; clamped to 7 days either side of receipt
displayobjectPresentation hints; keys below
display.lifetimenumber or stringWhole seconds 1..86400, or "sticky" (shown until dismissed)
display.layoutstringsplit or inline
display.effectstringnone, blink, flash, highlight, wave or gradient
display.targetstringemphasis (the bold spans) or message (the whole line)
display.periodnumberEffect cycle in seconds, 0.5..10
display.colorstringAccent for emphasised spans: #rgb, #rrggbb or #rrggbbaa
dataobjectJSON object, at most 8 KB
idempotency_keystring1 to 128 characters; a key used again on the source within 7 days returns the original event
sourcestringMust equal the key's source slug; leave out

An unknown field or display key fails with 400 unknown_field.

Request

curl

curl https://api.albastov.com/v1/events \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "backup.done", "text": "Backup **done**, 412 MB", "idempotency_key": "backup-2026-11-03"}'

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events",
    data=json.dumps({"type": "backup.done", "text": "Backup **done**, 412 MB", "idempotency_key": "backup-2026-11-03"}).encode(),
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_KEY']}", "Content-Type": "application/json"},
    method="POST",
)
print(json.load(urllib.request.urlopen(req, timeout=5)))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ type: "backup.done", text: "Backup **done**, 412 MB", idempotency_key: "backup-2026-11-03" }),
  signal: AbortSignal.timeout(5000),
});
console.log(res.status, await res.json());

Response

201 with the stored event; 200 with the original event when the source used idempotency_key in the last 7 days.

Response
{"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-11-03T10:02:03Z", "received_at": "2026-11-03T10:02:03Z", "display": null, "data": null, "dismissed_at": null}

Errors

StatusCodeMeaning
400bad_jsonThe body is not one JSON object
400unknown_fieldA field or display key outside the table; meta.field names it
400invalid_fieldA field fails validation; meta.field names it
401publish_key_requiredNo Authorization header
401invalid_keyUnknown or revoked key
403scope_missingThe key carries no publish scope: a read key or an account key
403account_cannotThe key's account cannot reach this route
403key_unboundThe key is not a publish key bound to one source
403source_mismatchsource names another source
403source_archivedThe key's source is archived
413body_too_largeBody over 16 KB
429rate_limitedOver the key's rate; wait Retry-After seconds

Publish a batch

POST /v1/events/batch stores 1 to 100 events in the publish key's source in one request.

Headers

NameDescription
AuthorizationBearer <publish key>
Content-Typeapplication/json
Retry-AfterResponse header on 429: seconds to wait

Parameters

NameTypeDescription
events requiredarray1 to 100 event objects, each with the fields of Publish an event, each at most 16 KB

One invalid event refuses the whole batch; the error's meta.index names the event. Each event of the batch counts against the key's event rate.

Request

curl

curl https://api.albastov.com/v1/events/batch \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"events": [{"type": "job.done", "text": "Job **export** done"}, {"type": "job.done", "text": "Job **import** done"}]}'

Python

import json, os, urllib.request

events = [{"type": "job.done", "text": "Job **export** done"}, {"type": "job.done", "text": "Job **import** done"}]
req = urllib.request.Request(
    "https://api.albastov.com/v1/events/batch",
    data=json.dumps({"events": events}).encode(),
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_KEY']}", "Content-Type": "application/json"},
    method="POST",
)
print(json.load(urllib.request.urlopen(req, timeout=5)))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events/batch", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    events: [
      { type: "job.done", text: "Job **export** done" },
      { type: "job.done", text: "Job **import** done" },
    ],
  }),
  signal: AbortSignal.timeout(5000),
});
console.log(res.status, await res.json());

Response

{"events": [...]} with the stored events in request order; status 201 when at least one event is new, else 200.

Response
{"events": [{"id": "evt_01JBX7Q2W8V5K3M9N4P6R2T8YD", "cursor": "1735", "source": "jobs", "source_name": "Jobs", "source_color": null, "source_icon": null, "type": "job.done", "severity": "info", "text": "Job **export** done", "detail": null, "subject": null, "url": null, "occurred_at": "2026-11-03T10:02:04Z", "received_at": "2026-11-03T10:02:04Z", "display": null, "data": null, "dismissed_at": null}, {"id": "evt_01JBX7Q2W8V5K3M9N4P6R2T8YE", "cursor": "1736", "source": "jobs", "source_name": "Jobs", "source_color": null, "source_icon": null, "type": "job.done", "severity": "info", "text": "Job **import** done", "detail": null, "subject": null, "url": null, "occurred_at": "2026-11-03T10:02:04Z", "received_at": "2026-11-03T10:02:04Z", "display": null, "data": null, "dismissed_at": null}]}

Errors

StatusCodeMeaning
400bad_jsonThe body is not {"events": [...]}
400unknown_fieldA top-level key other than events, or an unknown field in an event; meta.field and meta.index name it
400invalid_fieldevents is not an array of 1 to 100 events, or an event fails validation; meta.field and meta.index name it
401publish_key_requiredNo Authorization header
401invalid_keyUnknown or revoked key
403scope_missingThe key carries no publish scope
403account_cannotThe key's account cannot reach this route
403key_unboundThe key is not a publish key bound to one source
403source_mismatchAn event's source names another source; meta.index names the event
403source_archivedThe key's source is archived
413body_too_largeBody over 256 KB, or one event over 16 KB (meta.index)
429rate_limitedOver the key's request or event rate; wait Retry-After seconds

Set the source display

PATCH /v1/events/sources/self sets the name, icon and colour of the publish key's own source, for the source's whole history.

Headers

NameDescription
AuthorizationBearer <publish key>
Content-Typeapplication/json

Parameters

NameTypeDescription
namestringAt most 80 characters; "" sets the name to the slug
iconstringA Simple Icons slug or an SF Symbol name, [a-z0-9.-]{1,48}; "" clears the icon
colorstring#rgb, #rrggbb or #rrggbbaa; "" clears the colour

Omitted fields keep their values; {} returns the current values.

Request

curl

curl -X PATCH https://api.albastov.com/v1/events/sources/self \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Sentry", "icon": "sentry", "color": "#362d59"}'

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events/sources/self",
    data=json.dumps({"name": "Sentry", "icon": "sentry", "color": "#362d59"}).encode(),
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_KEY']}", "Content-Type": "application/json"},
    method="PATCH",
)
print(json.load(urllib.request.urlopen(req)))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events/sources/self", {
  method: "PATCH",
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Sentry", icon: "sentry", color: "#362d59" }),
});
console.log(await res.json());

Response

Response
{"slug": "sentry", "name": "Sentry", "icon": "sentry", "color": "#362d59"}

Errors

StatusCodeMeaning
400bad_jsonThe body is not a JSON object of name, icon and color
400invalid_fieldname, icon or color fails validation; meta.field names it
401publish_key_requiredNo Authorization header
401invalid_keyUnknown or revoked key
403scope_missingThe key carries no publish scope
403account_cannotThe key's account cannot reach this route
403key_unboundThe key is not a publish key bound to one source
403source_archivedThe key's source is archived
413body_too_largeBody over 16 KB
429rate_limitedOver the key's rate; wait Retry-After seconds

Receive a webhook delivery

POST /v1/events/hooks/in/{token} runs a request from another app through the source's mapping and publishes the resulting events. Mapping language, states and duplicates: Webhooks.

Headers

NameDescription
Signature headerThe header the webhook's signature names (X-Signature by default, Stripe-Signature for scheme stripe); required when the signature is required
Idempotency-Key, Webhook-Id, X-Webhook-Id, X-Delivery-Id, X-GitHub-DeliveryOptional delivery id; dedupes events without a mapped idempotency
Retry-AfterResponse header on 429: seconds to wait

Parameters

NameTypeDescription
token requiredstringPath: the webhook's token, 43 characters, from the admin panel
BodyanyJSON, a form, or any text, at most 2 MiB; an empty body reads the query string

Request

curl

curl https://api.albastov.com/v1/events/hooks/in/$ALBASTOV_WEBHOOK_TOKEN \
  -H "Content-Type: application/json" \
  -d '{"repository": {"name": "shop"}, "build": {"id": 4812, "number": 312, "status": "failed", "branch": "main"}}'

Python

import json, os, urllib.request

req = urllib.request.Request(
    f"https://api.albastov.com/v1/events/hooks/in/{os.environ['ALBASTOV_WEBHOOK_TOKEN']}",
    data=json.dumps({"repository": {"name": "shop"}, "build": {"id": 4812, "number": 312, "status": "failed", "branch": "main"}}).encode(),
    headers={"User-Agent": "my-app/1.0", "Content-Type": "application/json"},
    method="POST",
)
print(json.load(urllib.request.urlopen(req)))

JavaScript

const res = await fetch(`https://api.albastov.com/v1/events/hooks/in/${process.env.ALBASTOV_WEBHOOK_TOKEN}`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ repository: { name: "shop" }, build: { id: 4812, number: 312, status: "failed", branch: "main" } }),
});
console.log(await res.json());

Response

200 with the delivery's outcome and the ids of the stored events.

Response
{"ok": true, "outcome": "2 events: evt_01JBX7Q2W8V5K3M9N4P6R2T8YC,dropped: rule 1,evt_01JBX7Q2W8V5K3M9N4P6R2T8YD", "events": ["evt_01JBX7Q2W8V5K3M9N4P6R2T8YC", "evt_01JBX7Q2W8V5K3M9N4P6R2T8YD"]}
outcomeMeaning
evt_…One event, stored
heldOne event, held by the noise budget; part of the next summary event
dropped: <reason>No event: a drop rule or case matched (rule 1, case 3, or the rule's reason)
dropped: duplicate of delivery <id>No event: the same body arrived within 10 minutes
held: pending reviewNo event: the webhook is pending; the delivery is kept for review
3 events: evt_a,held,dropped: rule 1One entry per item of a split
…; url dropped: host <host> not allowedA mapped url outside the allowed hosts was removed
…; 12 items over 1000 droppedA split array was longer than 1000
…; mapping error: <message>The mapping failed; the delivery made the fallback event

Errors

StatusCodeMeaning
400bad_deliveryAn enricher refused the delivery, such as a malformed uptime report
401bad_signatureMissing or invalid signature where one is required or sent
404hook_not_foundUnknown token, webhook turned off, or source archived
413body_too_largeBody over 2 MiB
429rate_limitedSource over 120 deliveries a minute, or the client address over its refusals; wait Retry-After seconds
503seal_key_missingThe webhook has a signature that cannot be checked right now; the delivery is refused

List events

GET /v1/events lists the read key's events, newest first; with after, oldest first.

Headers

NameDescription
AuthorizationBearer <read key>
Retry-AfterResponse header on 429: seconds to wait

Parameters

NameTypeDescription
sourcestringQuery: comma-separated source slugs; default every granted source
typestringQuery: exact event type, [a-z0-9._-]{1,48}
severity_minstringQuery: lowest severity, debug, info, notice, warn, error or critical
qstringQuery: text search, at most 200 characters
afterstringQuery: cursor; events after the cursor, oldest first
beforestringQuery: cursor; events before the cursor, newest first
limitintegerQuery: 1..500; default 100
stickystringQuery: open lists sticky events this key has not dismissed

Request

curl

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

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events?limit=5",
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_READ_KEY']}"},
)
print(json.load(urllib.request.urlopen(req)))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events?limit=5", {
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_READ_KEY}` },
});
console.log(await res.json());

Response

NameTypeDescription
eventsarrayEvents, with dismissed_at for this key
next_beforestring or nullCursor of the next older page; null when the page holds fewer than limit events, and with after
latest_cursorstringNewest cursor among the requested sources; pass as after or Last-Event-ID to resume
Response
{"events": [{"id": "evt_01JBX7Q2W8V5K3M9N4P6R2T8YC", "cursor": "1734", "source": "github", "source_name": "GitHub", "source_color": "#181717", "source_icon": "github", "type": "github.push", "severity": "info", "text": "shop: 2 commits by Dana — Fix login redirect", "detail": "1a2b3c4 Fix login redirect\n9f3c2e1 Add invoice export", "subject": "shop", "url": "https://github.com/example-org/shop/compare/1a2b3c4d5e6f...9f3c2e1aa0b1", "occurred_at": "2026-11-03T10:01:00Z", "received_at": "2026-11-03T10:01:02Z", "display": null, "data": null, "dismissed_at": null}], "next_before": null, "latest_cursor": "1734"}

Errors

StatusCodeMeaning
400invalid_fieldA bad parameter; meta.field names it
400unknown_sourcesource names no source (keys granted every source)
401unauthorizedNo Authorization header
401invalid_keyUnknown or revoked key
403scope_missingThe key carries no read scope: a publish key or an account key
403account_cannotThe key's account cannot reach this route
403key_unboundThe key is not a read key
403source_not_grantedsource names a source outside the key's grant
429rate_limitedOver the key's rate; wait Retry-After seconds

Get key info

GET /v1/events/info returns the calling key's kind, name and sources.

Headers

NameDescription
AuthorizationBearer <read key> or Bearer <publish key>

Request

curl

curl https://api.albastov.com/v1/events/info \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_READ_KEY"

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events/info",
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_READ_KEY']}"},
)
print(json.load(urllib.request.urlopen(req)))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events/info", {
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_READ_KEY}` },
});
console.log(await res.json());

Response

NameTypeDescription
protocolstringambient/1
namestringThe hub's name
keystringreader or publish
readerstring or nullThe read key's name; null for a publish key
sourcesarraySource slugs the key reaches
featuresarraybatch, search
docsstringURL of the Events guide as markdown (GET /v1/events/docs)
Response
{"protocol": "ambient/1", "name": "Albastar Citadel", "key": "reader", "reader": "ops-laptop", "sources": ["github", "shop"], "features": ["batch", "search"], "docs": "…/v1/events/docs"}

Errors

StatusCodeMeaning
401events_key_requiredNo Authorization header
401invalid_keyUnknown or revoked key
403scope_missingThe key carries no events scope
403account_cannotThe key's account cannot reach this route
403key_unboundThe key is not bound as a read key or a publish key
429rate_limitedOver the key's rate; wait Retry-After seconds

List sources

GET /v1/events/sources returns the read key's granted sources as an array.

Headers

NameDescription
AuthorizationBearer <read key>

Request

curl

curl https://api.albastov.com/v1/events/sources \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_READ_KEY"

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events/sources",
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_READ_KEY']}"},
)
print(json.load(urllib.request.urlopen(req)))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events/sources", {
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_READ_KEY}` },
});
console.log(await res.json());

Response

NameTypeDescription
slugstringSource slug
namestringSource name
colorstring or nullSource colour
iconstring or nullSimple Icons slug or SF Symbol name
last_event_atstring or nullTime of the newest event, RFC 3339 UTC
count_24hnumberEvents in the last 24 hours
archivedbooleantrue when the source takes no new events
Response
[{"slug": "github", "name": "GitHub", "color": "#181717", "icon": "github", "last_event_at": "2026-11-03T10:01:02Z", "count_24h": 14, "archived": false}]

Errors

StatusCodeMeaning
401unauthorizedNo Authorization header
401invalid_keyUnknown or revoked key
403scope_missingThe key carries no read scope
403account_cannotThe key's account cannot reach this route
403key_unboundThe key is not a read key
429rate_limitedOver the key's rate; wait Retry-After seconds

Stream events

GET /v1/events/stream returns a text/event-stream of new events of the read key's sources.

Headers

NameDescription
AuthorizationBearer <read key>
Last-Event-IDOptional cursor; sends the events after the cursor first; takes precedence over after
Content-TypeResponse header: text/event-stream; charset=utf-8

Parameters

NameTypeDescription
sourcestringQuery: comma-separated source slugs; default every granted source
afterstringQuery: cursor; as Last-Event-ID

Request

curl

curl -N https://api.albastov.com/v1/events/stream \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_READ_KEY"

Python

import os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events/stream",
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_READ_KEY']}"},
)
with urllib.request.urlopen(req) as res:
    for line in res:
        print(line.decode().rstrip("\n"))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events/stream", {
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_READ_KEY}` },
});
const decoder = new TextDecoder();
for await (const bytes of res.body) process.stdout.write(decoder.decode(bytes, { stream: true }));

Response

Frames
retry: 3000
: connected

id: 1734
event: event
data: {"id":"evt_01JBX7Q2W8V5K3M9N4P6R2T8YC","cursor":"1734",…}

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

event: gap
data: {"after":"1200","skipped":2311,"resume":"3511"}

: keepalive
FrameMeaning
retry: 3000, : connectedSent once on connect
eventOne event; id is the event's cursor
stateThe key dismissed (dismissed_at set) or restored (dismissed_at: null) an event
gapMore than 1000 events were pending after the cursor: the skipped events after cursor after, up to and including cursor resume, were not sent; fetch them with GET /v1/events?after=<after>
: keepaliveComment every 20 s

With a cursor, the stream sends the pending events (the newest 1000 after a gap), then this key's dismissals since the cursor as state frames, then new events.

Errors

StatusCodeMeaning
400invalid_fieldafter or Last-Event-ID is not a cursor; meta.field names it
400unknown_sourcesource names no source (keys granted every source)
401unauthorizedNo Authorization header
401invalid_keyUnknown or revoked key
403scope_missingThe key carries no read scope
403account_cannotThe key's account cannot reach this route
403key_unboundThe key is not a read key
403source_not_grantedsource names a source outside the key's grant
429too_many_streamsThe key already holds 8 open streams
429rate_limitedOver the key's rate; wait Retry-After seconds

Dismiss an event

POST /v1/events/{id}/dismiss sets the read key's dismissal of an event and returns the event.

Headers

NameDescription
AuthorizationBearer <read key>

Parameters

NameTypeDescription
id requiredstringPath: event id, evt_…

A second POST keeps the first dismissal time. Every open stream of the same key receives a state frame.

Request

curl

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

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events/evt_01JBX7Q2W8V5K3M9N4P6R2T8YC/dismiss",
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_READ_KEY']}"},
    method="POST",
)
print(json.load(urllib.request.urlopen(req)))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events/evt_01JBX7Q2W8V5K3M9N4P6R2T8YC/dismiss", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_READ_KEY}` },
});
console.log(await res.json());

Response

The event with dismissed_at set.

Response
{"id": "evt_01JBX7Q2W8V5K3M9N4P6R2T8YC", "cursor": "1734", "source": "web", "source_name": "Web", "source_color": null, "source_icon": null, "type": "deploy.failed", "severity": "error", "text": "Deploy **failed** on web `9f3c2e1`", "detail": "npm run build exited 1", "subject": null, "url": null, "occurred_at": "2026-11-03T10:02:03Z", "received_at": "2026-11-03T10:02:03Z", "display": {"lifetime": "sticky"}, "data": null, "dismissed_at": "2026-11-03T10:05:00Z"}

Errors

StatusCodeMeaning
401unauthorizedNo Authorization header
401invalid_keyUnknown or revoked key
403scope_missingThe key carries no read scope
403account_cannotThe key's account cannot reach this route
403key_unboundThe key is not a read key
404not_foundNo event with that id in the key's grant
429rate_limitedOver the key's rate; wait Retry-After seconds

Restore an event

DELETE /v1/events/{id}/dismiss clears the read key's dismissal of an event and returns the event with dismissed_at: null. Headers, parameters and errors match Dismiss an event; a second DELETE changes nothing.

Request

curl

curl -X DELETE https://api.albastov.com/v1/events/evt_01JBX7Q2W8V5K3M9N4P6R2T8YC/dismiss \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_READ_KEY"

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events/evt_01JBX7Q2W8V5K3M9N4P6R2T8YC/dismiss",
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_READ_KEY']}"},
    method="DELETE",
)
print(json.load(urllib.request.urlopen(req)))

JavaScript

const res = await fetch("https://api.albastov.com/v1/events/evt_01JBX7Q2W8V5K3M9N4P6R2T8YC/dismiss", {
  method: "DELETE",
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_READ_KEY}` },
});
console.log(await res.json());

Get the Events guide

GET /v1/events/docs returns the Events guides as one markdown document, without a key.

Headers

NameDescription
Content-TypeResponse header: text/markdown; charset=utf-8
Cache-ControlResponse header: public, max-age=300

Request

curl

curl https://api.albastov.com/v1/events/docs

Python

import urllib.request

print(urllib.request.urlopen(urllib.request.Request("https://api.albastov.com/v1/events/docs", headers={"User-Agent": "my-app/1.0"})).read().decode()[:400])

JavaScript

const res = await fetch("https://api.albastov.com/v1/events/docs");
console.log((await res.text()).slice(0, 400));

Response

The Overview, Publishing, Webhooks and Receiving pages in sidebar order, each under its title and page URL, without front matter.