Events
Event feed, event detail, deduped group lookup, and the public SSE stream.
GET /v1/events
List DeFi events with filters, cursor-style metadata, and full-text search.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | number | 1 | Page number |
| limit | number | 20 | Results per page |
| eventType | string | - | Event type such as Incident or DepositabilityChange |
| severity | string | - | Critical, Warning, or Info |
| protocolId | string | - | Protocol ID |
| curatorId | string | - | Curator ID |
| chainId | string | - | Chain: integer id or slug (e.g. 1, solana) |
| incidentSeverity | string | - | minor or major |
| remediationStatus | string | - | resolved or unresolved |
| search | string | - | Full-text search on title and description |
| excludeSource | string | - | Exclude a source such as peg-monitor |
| sortBy | string | occurred_at | Sort field |
| sortOrder | string | desc | asc or desc |
| daysBack | string | adaptive | Lookback window, capped at 730 days |
Example
curl "https://api.philidor.io/v1/events?severity=Critical&limit=5"Response
{
"data": [
{
"id": "42",
"event_type": "Incident",
"severity": "Critical",
"title": "Vault incident",
"description": "Incident summary",
"occurred_at": "2026-07-01T12:00:00Z",
"protocol_id": "morpho",
"protocol_name": "Morpho",
"chain_id": 1,
"chain_name": "Ethereum",
"curator_id": "gauntlet",
"curator_name": "Gauntlet",
"incident_severity": "major",
"loss_amount_usd": null,
"remediation_status": "unresolved",
"links": [],
"occurrence_count": 1,
"group_key": "incident:morpho:42"
}
],
"meta": {
"page": 1,
"limit": 5,
"hasMore": false
}
}GET /v1/events/{id}
Get one event with affected vaults.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | string | Event ID |
Example
curl https://api.philidor.io/v1/events/42Response
{
"data": {
"id": "42",
"event_type": "Incident",
"severity": "Critical",
"title": "Vault incident",
"affected_vaults": [
{
"vault_id": "morpho-1-0x...",
"vault_name": "Gauntlet USDC Core",
"tvl_usd": 125000000,
"chain_id": 1,
"address": "0x..."
}
]
}
}GET /v1/events/group/{groupKey}
Return the current representative for a deduped event group. This helps clients recover when a grouped event is retracted.
Example
curl https://api.philidor.io/v1/events/group/rating-change:morpho:2026-06-22Response
Same shape as a single event list item.
GET /v1/events/stream
Open a Server-Sent Events stream. Requires a Data-plan key. Browser EventSource cannot send an Authorization header — connect through a server-side relay or pass the key on the underlying request as shown in API Access and Plans.
The stream flushes ready immediately, then delivers immutable operation snapshots named published, promoted, or retracted. Each operation has a versioned delivery cursor such as id: v2:42. The event row's data.id remains its event ID. Republication of the same event produces a new cursor and a new captured body.
Heartbeat comments arrive every 20 seconds. Connection caps are 200 per key and 5 per end viewer when a keyed relay reports X-Philidor-Client-Ip. Anonymous connections retain a 5-per-IP cap during a configured rollout window. Rejected admissions return finite JSON with a correlated request ID, Cache-Control: no-store, and a Retry-After hint; the connection-cap hint is 10 seconds.
Reconnecting and checkpoints
Send the last delivered v2:<cursor> as Last-Event-ID, or as ?since= when opening a replacement relay connection. Last-Event-ID takes precedence. Delivery cursors are 64-bit decimal strings; do not convert them to JavaScript numbers.
Live delivery and replay consume the same durable operation journal. Polling recovers committed operations even when a notification is missed. Snapshot bodies preserve the operation as captured, rather than hydrating the event's current state on reconnect. Current publication restrictions and chain access gates still apply.
Replay is bounded to a 24-hour delivery window and 1000 journal operations per connection. Reconnect delivery is at least once; deduplicate repeated delivery cursors. An event ID and operation name are not a sufficient deduplication key because the same event can be retracted and published again.
A cursor control frame advances the replay checkpoint over filtered operations without emitting their bodies. Persist its id just as you persist an operation's id. For a new connection without a cursor, the checkpoint starts at the current journal tail; load the initial event list through REST.
The version 2 ready frame reports the supplied cursor and limits:
{
"vaultId": null,
"version": 2,
"replay": { "cursor": "v2:42", "window_seconds": 86400, "max_rows": 1000 }
}A gap frame explicitly reports an incomplete range. It has no new id line.
reason | Meaning | Recovery |
|---|---|---|
queue_overflow | The client exhausted its bounded 100-frame queue. Queued frames precede the gap, then the connection closes. | Reconnect from the last delivered cursor. The gap also carries the last queued checkpoint. |
replay_truncated | The 1000-operation replay limit was reached before the journal tail. The connection closes after its checkpoint and gap. | Reconnect using the gap's cursor to continue the remaining range. |
cursor_legacy | An unversioned event ID or timestamp belongs to the previous cursor contract. | Refetch REST state. The server also replays the retained journal window; pre-journal operations cannot be reconstructed. |
cursor_unknown, cursor_invalid | The supplied cursor is not a valid retained delivery position. | Refetch REST state and accept the retained-window checkpoint. |
cursor_expired | The cursor precedes the 24-hour replay window. | Refetch older state; the retained window is replayed from its floor. |
replay_failed | The journal could not be read. The connection closes. | Retry from the last delivered cursor with backoff and refetch REST state if recovery remains unavailable. |
The journal begins with this release; it does not backfill past event mutations. Previously issued numeric and timestamp cursors remain recognized as legacy positions and produce an explicit gap. The API's reviewed stream handoff sends draining with retry: 1000; replay and checkpoint handling are required for reconnect continuity within the retained window.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| vaultId | string | - | Optional vault filter |
| since | string | - | Delivery cursor v2:<64-bit decimal string>. Legacy numeric/timestamp positions produce an explicit gap. Last-Event-ID wins when both are present. |
Example
// This relay attaches a server-held Data-plan API key.
const stream = new EventSource('/api/philidor/events-stream');
stream.addEventListener('published', (event) => {
const row = JSON.parse(event.data);
console.log(row.title);
});Response
event: ready
data: {"vaultId":null,"version":2,"replay":{"cursor":null,"window_seconds":86400,"max_rows":1000}}
id: v2:42
event: published
data: {"id":42,"title":"Vault incident"}