Skip to content

Read platform notifications

Platform notifications include app-expiry warnings addressed to the current person. They are separate from agent conversations, task events and completion callbacks. Reading them does not mark an agent thread read.

Require notifications.structured_inbox in server discovery. Use an authenticated current person in the selected organization. API credentials need current app-read scope and remain limited by their organization and team. The server rechecks membership, credentials, current app access and warning preferences on every query and stream poll.

Catch up and follow

query CatchUp($after: String) {
  astroliftMyNotificationsPage(after: $after, limit: 100) {
    items {
      id kind title body link readAt createdAt
      expiryWarning { appId appSlug expiresAt warningWindow }
    }
    endCursor
    hasMore
  }
}

subscription Follow($after: String) {
  astroliftMyNotificationStream(after: $after) {
    items {
      id kind title body link readAt createdAt
      expiryWarning { appId appSlug expiresAt warningWindow }
    }
    endCursor
    hasMore
  }
}

after: null starts at the beginning of retained history. Process a whole batch, persist its endCursor, and continue while hasMore is true. Deduplicate by notification id. The query scans at most 100 rows per batch, default 50. Authorization can hide rows, so an empty batch can still advance its cursor. Always retain the new cursor and obey hasMore.

The subscription drains catch-up batches and then checks the database every two seconds. An initial empty batch supplies a cursor for an empty inbox. After a disconnect, reconnect with the last fully processed cursor. Reads are at least once while the notification is retained and the person remains authorized; there is no client acknowledgment or indefinite retention promise.

The WebSocket endpoint is wss://<install>/app/gql/config/ws/, using subprotocol graphql-transport-ws. Send connection_init with payload {"authorization":"Bearer <person API token>"}, or use an existing authorized browser session. Wait for connection_ack before subscribing. Keep credentials out of URLs. Authorization errors terminate the operation.

The existing astro api graphql transport can execute the catch-up query with the person's configured credential. This API adds no CLI streaming command. The legacy notificationReceived subscription reads a different notification store and is not an expiry-warning feed.

Cursors, visibility and warning history

A cursor is signed and bound to the installation, organization, person and credential ceiling. A different credential, team or scope set must restart with null. Invalid cursors are rejected. Gaining access does not rewind an existing cursor: restart with null to rescan retained history and deduplicate IDs.

Expiry warnings require current app-read access, current creator/team-recipient eligibility and no explicit per-app app_expiring mute. Warnings disappear from these reads when their app or owner becomes unavailable. Team-bound credentials see only notifications whose structured provenance proves their app scope. Historical warnings without that provenance are omitted; the server does not infer authorization from human text or links.

expiryWarning records the app's GUID and slug, absolute expiry, and emitted window (24h or 1h). Only the most specific due unsent window emits; missed earlier windows do not create additional warnings. Extending an expiry does not rewrite old rows. Display the recorded expiry as historical warning context and fetch current app state before offering an extension.

Reading or streaming does not change read state. Use markNotificationRead or markAllNotificationsRead explicitly. Read-state changes are not new stream events. Personal quiet hours suppress agent-message email/push only; app-expiry warnings keep their own delivery preferences. No inbox receipt proves an external channel delivered a message.