Notifications API
The NotificationService gRPC service creates, lists, and manages operator-facing Wendy Notifications in a Wendy Cloud organization.
Notifications API
The NotificationService gRPC service creates, lists, and manages
operator-facing Wendy Notifications in a Wendy Cloud organization.
Proto package
wendycloud.v1 — defined in Proto/cloud/notifications.proto.
App-facing API (wendy.system.v1)
Apps with the notifications entitlement
call wendy.system.v1.NotificationService over the Unix socket at
$WENDY_SYSTEM_SOCKET (/run/wendy/system/system.sock). This creates a
canonical Wendy Notification in the recipients' Companion inboxes. Cloud then
attempts APNs delivery; apps do not send an arbitrary APNs payload directly.
WendyKit is currently the only application SDK for this API, and it is Swift-only. Apps written in other languages call this gRPC service directly. See Send notifications from a device app for entitlement and Cloud grant setup, Swift and direct gRPC examples, and delivery behavior.
The private socket binds every call to trusted app identity. The request cannot supply an app ID, device ID, or organization ID; the agent adds app identity and Cloud derives device and organization identity from device mTLS.
Send
Send(SendRequest) → SendResponseThe agent forwards the request to Cloud. notification_id is the caller-chosen resource
identity, not a retry token. After successful creation, every reuse of its
canonical UUID—including an otherwise identical request or a differently cased
spelling—returns ALREADY_EXISTS; the prior success is never replayed.
Local validation and rate-limit failures happen before forwarding and do not
claim the UUID. After correcting the request or waiting for the local rate limit,
the caller may retry with the same notification_id.
SendRequest
| Field | Type | Description |
|---|---|---|
audience | NotificationAudience | Union of the user, organization team, and organization role selectors below. |
title | string | Notification title. |
body | string | Notification body. |
severity | NotificationSeverity | INFO, WARNING, ERROR, or CRITICAL. |
deep_link | string | Absolute wendy:// URI. |
notification_id | string | Caller-chosen Notification resource UUID v4. Cloud returns canonical lowercase; any canonical reuse after creation returns ALREADY_EXISTS. |
metadata | optional Struct | Structured JSON-compatible metadata. |
NotificationAudience
The app-facing and Cloud messages use the same plural selector shape. All three fields have union semantics. At most 100 selector entries may be supplied across the three lists. Cloud normalizes and deduplicates them, remains authoritative for recipient resolution, and resolves at most 100 recipients for a device-app send.
| Field | Type | Description |
|---|---|---|
user_ids | repeated string | User IDs to include. |
team_ids | repeated int32 | Organization team IDs to include. |
roles | repeated OrganizationRole | Organization roles to include. |
At least one selector is required. A user selected through more than one field receives one Notification.
SendResponse
| Field | Type | Description |
|---|---|---|
notification_id | string | Canonical lowercase UUID of the newly created Notification resource. |
Recipient totals are intentionally omitted because team and role counts can disclose organization membership.
Device-app sources may create 10 accepted Notifications per minute, and the
agent also smooths bursts locally. Repeated rate-limit violations can quarantine
that app/device source for 15 minutes.
Device-originated deep links are restricted to the source device;
wendy://devices/current/live is the portable form. For device-originated APNs
alerts, Cloud uses Wendy · <Cloud app name> as the banner title while retaining
the supplied title on the stored Notification.
Notification message
Fields 1–8 are the stable legacy Companion wire contract. V2 adds canonical
identity, content, audience, and created_by attribution without changing those
legacy fields.
| Field | Type | Description |
|---|---|---|
id | int32 | Legacy numeric ID. |
user_id | string | Recipient user ID in legacy and per-user read views. |
organization_id | int32 | Organization ID. |
body | string | Notification body. |
severity | NotificationSeverity | Severity level. |
related_entities | Struct | Legacy structured context. |
created_at | Timestamp | Creation time. |
deleted_at | optional Timestamp | Soft-deletion time. |
title | string | Notification title. |
deep_link | string | Absolute wendy:// URI. |
notification_id | optional string | Canonical caller-chosen resource UUID v4; absent on legacy records. |
metadata | Struct | Structured JSON-compatible metadata. |
audience | optional NotificationAudience | Original normalized selector union. |
created_by_user_id | optional string | Authenticated user that created the Notification. |
created_by_asset_id | optional int32 | Authenticated device asset that created the Notification. |
created_by_app_id | optional string | Trusted app identity stamped by the Wendy agent. |
Cloud API methods
CreateNotification (legacy)
CreateNotification(CreateNotificationRequest) → NotificationCreates a Notification for one user. This RPC and CreateNotificationRequest
are deprecated in their protobuf descriptors and remain intact for existing
Dashboard and legacy clients. wendycloud.v2 is no longer hypothetical — it is
vendored at Proto/wendycloud/v2/ and generated into go/proto/gen/cloudpb/v2/
— and this RPC is still carried there, still marked deprecated. The migration
marker records that it is to be removed from v2, not that it already has been.
CreateNotificationV2
CreateNotificationV2(CreateNotificationV2Request) → CreateNotificationV2ResponseClaims the caller-chosen canonical UUID and creates one Notification resource for
the resolved recipient union. The first canonical UUID use may succeed; every
later use returns ALREADY_EXISTS, regardless of whether request content is
identical or changed. Cloud does not replay the original response or send pushes
a second time.
User-authenticated callers provide organization_id. Provisioned-device callers
omit it because Cloud derives the organization and device from their certificate;
the Wendy agent stamps app_id from trusted container state.
CreateNotificationV2Request
| Field | Type | Description |
|---|---|---|
organization_id | optional int32 | Required for user-authenticated callers; omitted by provisioned devices. |
audience | NotificationAudience | Union of repeated user_ids, team_ids, and roles; see above. |
title | string | Notification title. |
body | string | Notification body. |
severity | NotificationSeverity | INFO, WARNING, ERROR, or CRITICAL. |
deep_link | string | Absolute wendy:// URI. |
notification_id | string | Caller-chosen Notification resource UUID v4; Cloud stores and returns canonical lowercase form and rejects every canonical reuse with ALREADY_EXISTS. |
metadata | optional Struct | Structured JSON-compatible metadata. |
app_id | optional string | Required for provisioned-device calls and stamped from trusted app identity by the Wendy agent. |
CreateNotificationV2Response
| Field | Type | Description |
|---|---|---|
notification_id | string | Canonical lowercase UUID of the newly created Notification resource. |
Recipient totals are intentionally omitted because team and role counts can disclose organization membership.
ListNotifications (server-streaming)
ListNotifications(ListNotificationsRequest) → stream ListNotificationsResponseReturns one Notification per streamed message.
ListNotificationsRequest
| Field | Type | Description |
|---|---|---|
organization_id | int32 | Organization to query. |
user_id | string | User whose Notifications are requested. |
offset | optional int32 | Number of matching Notifications to skip. |
limit | optional int32 | Maximum number of Notifications to return. |
severity_filter | optional NotificationSeverity | Restrict results by severity. |
include_deleted | bool | Include soft-deleted Notifications. |
ListNotificationsResponse
| Field | Type | Description |
|---|---|---|
notification | Notification | Notification carried by this stream message. |
total | int32 | Total number of matching Notifications. |
Migration note:
ListNotificationspreviously returned one unary response containingrepeated notifications,next_page_token, andtotal_count. Clients must now receive the server stream until it closes and useoffset/limitinstead ofpage_size/page_token.
GetNotification
GetNotification(GetNotificationRequest) → NotificationReturns one Notification by ID.
DeleteNotification
DeleteNotification(DeleteNotificationRequest) → DeleteNotificationResponseDeletes one Notification by ID.
MarkAsRead
MarkAsRead(MarkAsReadRequest) → MarkAsReadResponseMarks the requested Notification IDs as read and returns the number marked.
Campaign notifications and catalog registration
A campaign with notify.on: event and notify.event: <name> sends an immediate
notification for the matching event. notify.on: detection sends on an inference
appearance. Omit notify.webhook for Wendy Cloud delivery, or supply an HTTP(S)
endpoint for webhook delivery. episode_committed remains manifest intent for
notification after upload; it is separate from immediate delivery.
Cloud delivery currently requires a legacy enrollment with positive numeric
organization and asset IDs. Experimental direct-PKI/OIDC enrollments with only
a SPIFFE principal cannot use this sender: it rejects them before sending with
FailedPrecondition, reported in inference_status.notification_error. Use an
explicit notify.webhook for immediate notifications on those devices.
The agent uses its enrolled device credentials and a bounded, nonpersistent
queue. There are at most three attempts, with 10-second timeouts and the same
event UUID. InvalidArgument, Unauthenticated, PermissionDenied,
FailedPrecondition, AlreadyExists, Unimplemented, and DataLoss stop
retries. A mismatched response notification ID is DataLoss. Other failures
are retried with backoff. Errors appear in agent logs and campaign
inference_status.notification_error.
App catalog API
The v1 AppService exposes UpsertApp, GetApp, UpdateApp, DeleteApp, and
ListApps. It replaces the former CreateApp RPC; clients must regenerate
against Proto/cloud/apps.proto. Mutations and GetApp carry both id and
organization_id. UpsertApp accepts optional name and details; UpdateApp
also accepts the owner/admin-controlled can_send_notifications grant.
ListApps uses organization_id, optional offset, limit, and filter,
returning apps and total. Pagination is offset-based, not page-token-based.
Deployment registration uses GetApp first and UpsertApp only when absent, so it
preserves existing metadata and never automatically grants notifications.
The vendored AppService contract matches Cloud's current v1 server. Earlier Companion schemas used different tags for update fields and page-token pagination, so those clients must be rebuilt with the current schema. The coordinated client ports are Companion SDK #1 and Companion iOS #223. The shared wire fixtures cover organization scoping, metadata updates, notification grants and offset pagination. These source changes do not upgrade installed clients; release the updated clients against the matching Cloud v1 contract. They do not change Cloud's separate v2 API.