> ## Documentation Index
> Fetch the complete documentation index at: https://docs.visual-layer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Notifications

> List, read, and manage alert notifications triggered by monitored saved views.

<Card title="How This Helps" icon="hand-platter">
  The Notifications API provides programmatic access to alert notifications generated by the monitoring system. Retrieve alerts, check unread counts, mark notifications as read, and subscribe to real-time updates via Server-Sent Events (SSE).
</Card>

## Prerequisites

* A **Visual Layer** Cloud account with API access.
* A valid JWT token. See [Authentication](/api-reference/authentication).
* At least one [saved view](/api-reference/saved-views) with alerting enabled, and a dataset that has received new media since the view was created.

<Note>
  Notifications are generated automatically when a monitored view with alerting enabled detects new matching results after a media batch is processed. The Notifications API is read-only — notifications cannot be created through the API.
</Note>

***

## List Notifications

Retrieve notifications for the authenticated user with pagination and filtering.

```http theme={"theme":"monokai"}
GET /api/v1/notifications
Authorization: Bearer <jwt>
```

### Query Parameters

<div className="integrations-table">
  | Parameter     | Type    | Default | Description                                     |
  | ------------- | ------- | ------- | ----------------------------------------------- |
  | `limit`       | integer | `20`    | Maximum notifications to return (1–100).        |
  | `offset`      | integer | `0`     | Number of notifications to skip for pagination. |
  | `unread_only` | boolean | `false` | When `true`, return only unread notifications.  |
  | `dataset_id`  | UUID    | `null`  | Filter notifications to a specific dataset.     |
</div>

### Example

```bash theme={"theme":"monokai"}
# List the 5 most recent notifications
curl -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications?limit=5"

# List unread notifications for a specific dataset
curl -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications?unread_only=true&dataset_id=<dataset_id>"
```

### Response (200)

```json theme={"theme":"monokai"}
{
  "notifications": [
    {
      "id": "e94ab07e-c0fe-4db1-83d0-34ea0fd8ee57",
      "user_id": "8f2f00c0-8446-49de-935f-0e9e058cb2a8",
      "entity_type": "saved_view",
      "entity_id": "94431831-ceeb-4e21-8c5b-84c78783bd4b",
      "title": "",
      "message": null,
      "metadata": {
        "batch_id": "a863a595-5aaf-45d3-89a9-073a73d59671",
        "is_alert": true,
        "view_name": "icecream",
        "dataset_name": "Ice cream",
        "result_count": 1875
      },
      "dataset_id": "a981fc0c-2d1f-11f1-a7ca-d2580fa2deac",
      "created_at": "2026-03-31T17:02:12.432489",
      "read_at": null
    }
  ],
  "total": 1,
  "has_more": false
}
```

### Notification Fields

<div className="integrations-table">
  | Field         | Type             | Description                                                                                      |
  | ------------- | ---------------- | ------------------------------------------------------------------------------------------------ |
  | `id`          | UUID             | Unique notification identifier.                                                                  |
  | `user_id`     | UUID             | The user this notification belongs to.                                                           |
  | `entity_type` | string           | Always `"saved_view"` for monitor alerts.                                                        |
  | `entity_id`   | UUID             | The saved view that triggered this alert.                                                        |
  | `title`       | string           | Notification title (currently empty for monitor alerts).                                         |
  | `message`     | string or null   | Optional notification message.                                                                   |
  | `metadata`    | object           | Alert details including `view_name`, `dataset_name`, `result_count`, `batch_id`, and `is_alert`. |
  | `dataset_id`  | UUID or null     | The dataset associated with this notification.                                                   |
  | `created_at`  | datetime         | When the notification was generated.                                                             |
  | `read_at`     | datetime or null | When the notification was marked as read. `null` if unread.                                      |
</div>

### Pagination

The response includes `total` (total matching notifications) and `has_more` (boolean indicating whether additional pages exist). Increment `offset` by `limit` to retrieve the next page.

```bash theme={"theme":"monokai"}
# Page 1
curl -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications?limit=20&offset=0"

# Page 2
curl -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications?limit=20&offset=20"
```

***

## Get Unread Count

Retrieve the number of unread notifications for the authenticated user. This endpoint is lightweight and suitable for polling to update badge counts.

```http theme={"theme":"monokai"}
GET /api/v1/notifications/unread-count
Authorization: Bearer <jwt>
```

### Query Parameters

<div className="integrations-table">
  | Parameter    | Type | Default | Description                                             |
  | ------------ | ---- | ------- | ------------------------------------------------------- |
  | `dataset_id` | UUID | `null`  | Count only unread notifications for a specific dataset. |
</div>

### Example

```bash theme={"theme":"monokai"}
# Total unread count across all datasets
curl -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications/unread-count"

# Unread count for a specific dataset
curl -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications/unread-count?dataset_id=<dataset_id>"
```

### Response (200)

```json theme={"theme":"monokai"}
{
  "count": 3
}
```

***

## Mark Notification as Read

Mark a single notification as read by its ID.

```http theme={"theme":"monokai"}
POST /api/v1/notifications/{notification_id}/read
Authorization: Bearer <jwt>
```

### Parameters

<div className="integrations-table">
  | Parameter         | Location | Type | Required | Description                       |
  | ----------------- | -------- | ---- | -------- | --------------------------------- |
  | `notification_id` | path     | UUID | Yes      | The notification to mark as read. |
</div>

### Example

```bash theme={"theme":"monokai"}
curl -X POST \
  -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications/<notification_id>/read"
```

### Response (200)

```json theme={"theme":"monokai"}
{
  "ok": true
}
```

Returns HTTP 404 if the notification does not exist, does not belong to the authenticated user, or is already read.

***

## Mark All Notifications as Read

Mark all notifications as read for the authenticated user. Optionally scope to a specific dataset.

```http theme={"theme":"monokai"}
POST /api/v1/notifications/read-all
Authorization: Bearer <jwt>
```

### Query Parameters

<div className="integrations-table">
  | Parameter    | Type | Default | Description                                                                                         |
  | ------------ | ---- | ------- | --------------------------------------------------------------------------------------------------- |
  | `dataset_id` | UUID | `null`  | Mark only notifications for this dataset as read. If omitted, all notifications are marked as read. |
</div>

### Example

```bash theme={"theme":"monokai"}
# Mark all notifications as read
curl -X POST \
  -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications/read-all"

# Mark only notifications for a specific dataset as read
curl -X POST \
  -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications/read-all?dataset_id=<dataset_id>"
```

### Response (200)

```json theme={"theme":"monokai"}
{
  "ok": true,
  "count": 5
}
```

The `count` field indicates how many notifications were marked as read.

***

## Real-Time Notification Stream (SSE)

Subscribe to a Server-Sent Events stream for real-time notification delivery. The server maintains a long-lived connection and pushes new notifications as they arrive.

```http theme={"theme":"monokai"}
GET /api/v1/notifications/stream
Authorization: Bearer <jwt>
```

### Query Parameters

<div className="integrations-table">
  | Parameter    | Type | Default | Description                                       |
  | ------------ | ---- | ------- | ------------------------------------------------- |
  | `dataset_id` | UUID | `null`  | Stream only notifications for a specific dataset. |
</div>

### Event Types

<div className="integrations-table">
  | Event          | Description                                                                        |
  | -------------- | ---------------------------------------------------------------------------------- |
  | `notification` | A new notification. The `data` field contains the full notification JSON object.   |
  | `ping`         | Keepalive event sent every 3 seconds. No meaningful data.                          |
  | `error`        | An internal error occurred. The connection remains open and retries automatically. |
</div>

### JavaScript Example

```javascript theme={"theme":"monokai"}
const eventSource = new EventSource(
  "https://app.visual-layer.com/api/v1/notifications/stream",
  { withCredentials: true }
);

eventSource.addEventListener("notification", (event) => {
  const notification = JSON.parse(event.data);
  console.log(`Alert: ${notification.metadata.view_name}`);
  console.log(`  ${notification.metadata.result_count} new matches`);
  console.log(`  Dataset: ${notification.metadata.dataset_name}`);
});

eventSource.addEventListener("ping", () => {
  // Connection is alive — no action needed
});

eventSource.addEventListener("error", (event) => {
  console.error("SSE error:", event);
});
```

### Python Example

```python theme={"theme":"monokai"}
import requests
import json

VL_BASE_URL = "https://app.visual-layer.com"
JWT_TOKEN = "<your-jwt-token>"

headers = {"Authorization": f"Bearer {JWT_TOKEN}"}

# Open SSE stream
resp = requests.get(
    f"{VL_BASE_URL}/api/v1/notifications/stream",
    headers=headers,
    stream=True,
)

for line in resp.iter_lines(decode_unicode=True):
    if line.startswith("data: ") and line.strip() != "data:":
        data = json.loads(line[6:])
        if "metadata" in data and data["metadata"].get("is_alert"):
            print(f"Alert: {data['metadata']['view_name']}")
            print(f"  {data['metadata']['result_count']} new results")
```

<Note>
  The SSE stream polls the database every 3 seconds. Notifications appear within one polling cycle of being generated. The connection stays open until the client disconnects.
</Note>

***

## Python Example: Full Workflow

```python theme={"theme":"monokai"}
import requests
import time

VL_BASE_URL = "https://app.visual-layer.com"
JWT_TOKEN = "<your-jwt-token>"
DATASET_ID = "<your-dataset-id>"

headers = {"Authorization": f"Bearer {JWT_TOKEN}"}

# Check unread count
resp = requests.get(
    f"{VL_BASE_URL}/api/v1/notifications/unread-count",
    headers=headers,
    params={"dataset_id": DATASET_ID},
)
resp.raise_for_status()
unread = resp.json()["count"]
print(f"Unread alerts: {unread}")

# List recent notifications for this dataset
resp = requests.get(
    f"{VL_BASE_URL}/api/v1/notifications",
    headers=headers,
    params={"dataset_id": DATASET_ID, "limit": 10, "unread_only": True},
)
resp.raise_for_status()
data = resp.json()

for n in data["notifications"]:
    meta = n["metadata"]
    print(f"  View: {meta['view_name']} — {meta['result_count']} new matches")

# Mark all as read for this dataset
if data["total"] > 0:
    resp = requests.post(
        f"{VL_BASE_URL}/api/v1/notifications/read-all",
        headers=headers,
        params={"dataset_id": DATASET_ID},
    )
    resp.raise_for_status()
    print(f"Marked {resp.json()['count']} notifications as read")
```

***

## Response Codes

See [Error Handling](/api-reference/errors) for the error response format and Python handling patterns.

<div className="integrations-table">
  | HTTP Code | Meaning                | Common Cause                                                                                     |
  | --------- | ---------------------- | ------------------------------------------------------------------------------------------------ |
  | **200**   | Request successful.    |                                                                                                  |
  | **401**   | Unauthorized.          | Invalid or expired JWT token.                                                                    |
  | **404**   | Not found.             | Notification does not exist, does not belong to the user, or is already read (for mark-as-read). |
  | **422**   | Validation error.      | Invalid query parameter values (for example, `limit` exceeding 100).                             |
  | **500**   | Internal server error. | Contact support if this persists.                                                                |
</div>

***

## Best Practices

* **Use `unread-count` for lightweight polling.** The unread count endpoint returns a single integer and is suitable for frequent polling to update UI badge counts without fetching full notification payloads.
* **Filter by `dataset_id` when possible.** Scoping requests to a specific dataset reduces response size and improves relevance, especially for users with access to many datasets.
* **Use SSE for real-time integrations.** The streaming endpoint provides sub-second alert delivery without polling overhead. Use it for dashboards, Slack integrations, or automated response workflows.
* **Mark alerts as read after processing.** Unread counts continue to grow if alerts are not acknowledged. Use `read-all` after reviewing a batch of notifications, or mark individual notifications as you process them.
* **Use `has_more` for pagination.** Check the `has_more` field before requesting additional pages. Requesting pages beyond the total returns empty results.

***

## Related Resources

<CardGroup cols={2}>
  <Card title="Saved Views API" icon="file-code-2" href="/api-reference/saved-views">
    Create views and configure monitoring and alerting.
  </Card>

  <Card title="Monitoring and Alerts" icon="blend" href="/docs/advanced-features/notifications">
    Understand how monitoring and alerting works across your datasets.
  </Card>

  <Card title="Add Media" icon="database" href="/api-reference/add-media-to-existing-dataset">
    Add media to datasets, triggering monitoring evaluation on all views.
  </Card>

  <Card title="Task Manager API" icon="list-checks" href="/api-reference/task-manager">
    Track media addition tasks that trigger monitoring evaluation.
  </Card>
</CardGroup>
