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.
| Route | Credential |
|---|---|
POST /v1/events, POST /v1/events/batch, PATCH /v1/events/sources/self | Publish 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}/dismiss | Read key |
GET /v1/events/info | Publish key or read key |
GET /v1/events/docs | None |
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:
{"ok": false, "error": "invalid-field", "code": "invalid_field", "message": "text is 312 characters; at most 300", "meta": {"field": "text"}}Event object
| Name | Type | Description |
|---|---|---|
id | string | Event id, evt_ and 26 characters |
cursor | string | Position in the store, a decimal string, strictly increasing |
source | string | Source slug |
source_name | string | Source name |
source_color | string or null | Source colour, #rgb, #rrggbb or #rrggbbaa |
source_icon | string or null | Simple Icons slug or SF Symbol name |
type | string | Event type |
severity | string | debug, info, notice, warn, error or critical |
text | string | One line |
detail | string or null | Plain text, newlines allowed |
subject | string or null | One line |
url | string or null | https:// URL |
occurred_at | string | RFC 3339 UTC |
received_at | string | RFC 3339 UTC |
display | object or null | Presentation hints as published |
data | object or null | JSON object as published |
dismissed_at | string or null | When 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
| Name | Description |
|---|---|
Authorization | Bearer <publish key> |
Content-Type | application/json |
Retry-After | Response header on 429: seconds to wait |
Parameters
| Name | Type | Description |
|---|---|---|
type required | string | [a-z0-9._-]{1,48}, a dotted noun: deploy.succeeded, pr.merged, site.down |
text required | string | One line, at most 300 characters; inline **bold**, *italic*, `code` |
severity | string | debug, info, notice, warn, error or critical; default info |
detail | string | Plain text, at most 1000 characters, newlines and tabs allowed |
subject | string | One line, at most 80 characters |
url | string | Absolute https:// URL, at most 2048 characters |
occurred_at | string | RFC 3339; default the time of receipt; clamped to 7 days either side of receipt |
display | object | Presentation hints; keys below |
display.lifetime | number or string | Whole seconds 1..86400, or "sticky" (shown until dismissed) |
display.layout | string | split or inline |
display.effect | string | none, blink, flash, highlight, wave or gradient |
display.target | string | emphasis (the bold spans) or message (the whole line) |
display.period | number | Effect cycle in seconds, 0.5..10 |
display.color | string | Accent for emphasised spans: #rgb, #rrggbb or #rrggbbaa |
data | object | JSON object, at most 8 KB |
idempotency_key | string | 1 to 128 characters; a key used again on the source within 7 days returns the original event |
source | string | Must 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.
{"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
| Status | Code | Meaning |
|---|---|---|
400 | bad_json | The body is not one JSON object |
400 | unknown_field | A field or display key outside the table; meta.field names it |
400 | invalid_field | A field fails validation; meta.field names it |
401 | publish_key_required | No Authorization header |
401 | invalid_key | Unknown or revoked key |
403 | scope_missing | The key carries no publish scope: a read key or an account key |
403 | account_cannot | The key's account cannot reach this route |
403 | key_unbound | The key is not a publish key bound to one source |
403 | source_mismatch | source names another source |
403 | source_archived | The key's source is archived |
413 | body_too_large | Body over 16 KB |
429 | rate_limited | Over 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
| Name | Description |
|---|---|
Authorization | Bearer <publish key> |
Content-Type | application/json |
Retry-After | Response header on 429: seconds to wait |
Parameters
| Name | Type | Description |
|---|---|---|
events required | array | 1 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.
{"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
| Status | Code | Meaning |
|---|---|---|
400 | bad_json | The body is not {"events": [...]} |
400 | unknown_field | A top-level key other than events, or an unknown field in an event; meta.field and meta.index name it |
400 | invalid_field | events is not an array of 1 to 100 events, or an event fails validation; meta.field and meta.index name it |
401 | publish_key_required | No Authorization header |
401 | invalid_key | Unknown or revoked key |
403 | scope_missing | The key carries no publish scope |
403 | account_cannot | The key's account cannot reach this route |
403 | key_unbound | The key is not a publish key bound to one source |
403 | source_mismatch | An event's source names another source; meta.index names the event |
403 | source_archived | The key's source is archived |
413 | body_too_large | Body over 256 KB, or one event over 16 KB (meta.index) |
429 | rate_limited | Over 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
| Name | Description |
|---|---|
Authorization | Bearer <publish key> |
Content-Type | application/json |
Parameters
| Name | Type | Description |
|---|---|---|
name | string | At most 80 characters; "" sets the name to the slug |
icon | string | A Simple Icons slug or an SF Symbol name, [a-z0-9.-]{1,48}; "" clears the icon |
color | string | #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
{"slug": "sentry", "name": "Sentry", "icon": "sentry", "color": "#362d59"}Errors
| Status | Code | Meaning |
|---|---|---|
400 | bad_json | The body is not a JSON object of name, icon and color |
400 | invalid_field | name, icon or color fails validation; meta.field names it |
401 | publish_key_required | No Authorization header |
401 | invalid_key | Unknown or revoked key |
403 | scope_missing | The key carries no publish scope |
403 | account_cannot | The key's account cannot reach this route |
403 | key_unbound | The key is not a publish key bound to one source |
403 | source_archived | The key's source is archived |
413 | body_too_large | Body over 16 KB |
429 | rate_limited | Over 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
| Name | Description |
|---|---|
| Signature header | The 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-Delivery | Optional delivery id; dedupes events without a mapped idempotency |
Retry-After | Response header on 429: seconds to wait |
Parameters
| Name | Type | Description |
|---|---|---|
token required | string | Path: the webhook's token, 43 characters, from the admin panel |
| Body | any | JSON, 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.
{"ok": true, "outcome": "2 events: evt_01JBX7Q2W8V5K3M9N4P6R2T8YC,dropped: rule 1,evt_01JBX7Q2W8V5K3M9N4P6R2T8YD", "events": ["evt_01JBX7Q2W8V5K3M9N4P6R2T8YC", "evt_01JBX7Q2W8V5K3M9N4P6R2T8YD"]}outcome | Meaning |
|---|---|
evt_… | One event, stored |
held | One 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 review | No event: the webhook is pending; the delivery is kept for review |
3 events: evt_a,held,dropped: rule 1 | One entry per item of a split |
…; url dropped: host <host> not allowed | A mapped url outside the allowed hosts was removed |
…; 12 items over 1000 dropped | A split array was longer than 1000 |
…; mapping error: <message> | The mapping failed; the delivery made the fallback event |
Errors
| Status | Code | Meaning |
|---|---|---|
400 | bad_delivery | An enricher refused the delivery, such as a malformed uptime report |
401 | bad_signature | Missing or invalid signature where one is required or sent |
404 | hook_not_found | Unknown token, webhook turned off, or source archived |
413 | body_too_large | Body over 2 MiB |
429 | rate_limited | Source over 120 deliveries a minute, or the client address over its refusals; wait Retry-After seconds |
503 | seal_key_missing | The 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
| Name | Description |
|---|---|
Authorization | Bearer <read key> |
Retry-After | Response header on 429: seconds to wait |
Parameters
| Name | Type | Description |
|---|---|---|
source | string | Query: comma-separated source slugs; default every granted source |
type | string | Query: exact event type, [a-z0-9._-]{1,48} |
severity_min | string | Query: lowest severity, debug, info, notice, warn, error or critical |
q | string | Query: text search, at most 200 characters |
after | string | Query: cursor; events after the cursor, oldest first |
before | string | Query: cursor; events before the cursor, newest first |
limit | integer | Query: 1..500; default 100 |
sticky | string | Query: 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
| Name | Type | Description |
|---|---|---|
events | array | Events, with dismissed_at for this key |
next_before | string or null | Cursor of the next older page; null when the page holds fewer than limit events, and with after |
latest_cursor | string | Newest cursor among the requested sources; pass as after or Last-Event-ID to resume |
{"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
| Status | Code | Meaning |
|---|---|---|
400 | invalid_field | A bad parameter; meta.field names it |
400 | unknown_source | source names no source (keys granted every source) |
401 | unauthorized | No Authorization header |
401 | invalid_key | Unknown or revoked key |
403 | scope_missing | The key carries no read scope: a publish key or an account key |
403 | account_cannot | The key's account cannot reach this route |
403 | key_unbound | The key is not a read key |
403 | source_not_granted | source names a source outside the key's grant |
429 | rate_limited | Over 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
| Name | Description |
|---|---|
Authorization | Bearer <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
| Name | Type | Description |
|---|---|---|
protocol | string | ambient/1 |
name | string | The hub's name |
key | string | reader or publish |
reader | string or null | The read key's name; null for a publish key |
sources | array | Source slugs the key reaches |
features | array | batch, search |
docs | string | URL of the Events guide as markdown (GET /v1/events/docs) |
{"protocol": "ambient/1", "name": "Albastar Citadel", "key": "reader", "reader": "ops-laptop", "sources": ["github", "shop"], "features": ["batch", "search"], "docs": "…/v1/events/docs"}Errors
| Status | Code | Meaning |
|---|---|---|
401 | events_key_required | No Authorization header |
401 | invalid_key | Unknown or revoked key |
403 | scope_missing | The key carries no events scope |
403 | account_cannot | The key's account cannot reach this route |
403 | key_unbound | The key is not bound as a read key or a publish key |
429 | rate_limited | Over 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
| Name | Description |
|---|---|
Authorization | Bearer <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
| Name | Type | Description |
|---|---|---|
slug | string | Source slug |
name | string | Source name |
color | string or null | Source colour |
icon | string or null | Simple Icons slug or SF Symbol name |
last_event_at | string or null | Time of the newest event, RFC 3339 UTC |
count_24h | number | Events in the last 24 hours |
archived | boolean | true when the source takes no new events |
[{"slug": "github", "name": "GitHub", "color": "#181717", "icon": "github", "last_event_at": "2026-11-03T10:01:02Z", "count_24h": 14, "archived": false}]Errors
| Status | Code | Meaning |
|---|---|---|
401 | unauthorized | No Authorization header |
401 | invalid_key | Unknown or revoked key |
403 | scope_missing | The key carries no read scope |
403 | account_cannot | The key's account cannot reach this route |
403 | key_unbound | The key is not a read key |
429 | rate_limited | Over 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
| Name | Description |
|---|---|
Authorization | Bearer <read key> |
Last-Event-ID | Optional cursor; sends the events after the cursor first; takes precedence over after |
Content-Type | Response header: text/event-stream; charset=utf-8 |
Parameters
| Name | Type | Description |
|---|---|---|
source | string | Query: comma-separated source slugs; default every granted source |
after | string | Query: 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
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| Frame | Meaning |
|---|---|
retry: 3000, : connected | Sent once on connect |
event | One event; id is the event's cursor |
state | The key dismissed (dismissed_at set) or restored (dismissed_at: null) an event |
gap | More 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> |
: keepalive | Comment 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
| Status | Code | Meaning |
|---|---|---|
400 | invalid_field | after or Last-Event-ID is not a cursor; meta.field names it |
400 | unknown_source | source names no source (keys granted every source) |
401 | unauthorized | No Authorization header |
401 | invalid_key | Unknown or revoked key |
403 | scope_missing | The key carries no read scope |
403 | account_cannot | The key's account cannot reach this route |
403 | key_unbound | The key is not a read key |
403 | source_not_granted | source names a source outside the key's grant |
429 | too_many_streams | The key already holds 8 open streams |
429 | rate_limited | Over 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
| Name | Description |
|---|---|
Authorization | Bearer <read key> |
Parameters
| Name | Type | Description |
|---|---|---|
id required | string | Path: 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.
{"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
| Status | Code | Meaning |
|---|---|---|
401 | unauthorized | No Authorization header |
401 | invalid_key | Unknown or revoked key |
403 | scope_missing | The key carries no read scope |
403 | account_cannot | The key's account cannot reach this route |
403 | key_unbound | The key is not a read key |
404 | not_found | No event with that id in the key's grant |
429 | rate_limited | Over 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
| Name | Description |
|---|---|
Content-Type | Response header: text/markdown; charset=utf-8 |
Cache-Control | Response header: public, max-age=300 |
Request
curl
curl https://api.albastov.com/v1/events/docsPython
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.