Skip to main content

How This Helps

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).

Prerequisites

  • A Visual Layer Cloud account with API access.
  • A valid JWT token. See Authentication.
  • At least one saved view with alerting enabled, and a dataset that has received new media since the view was created.
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.

List Notifications

Retrieve notifications for the authenticated user with pagination and filtering.
GET /api/v1/notifications
Authorization: Bearer <jwt>

Query Parameters

ParameterTypeDefaultDescription
limitinteger20Maximum notifications to return (1–100).
offsetinteger0Number of notifications to skip for pagination.
unread_onlybooleanfalseWhen true, return only unread notifications.
dataset_idUUIDnullFilter notifications to a specific dataset.

Example

# 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)

{
  "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

FieldTypeDescription
idUUIDUnique notification identifier.
user_idUUIDThe user this notification belongs to.
entity_typestringAlways "saved_view" for monitor alerts.
entity_idUUIDThe saved view that triggered this alert.
titlestringNotification title (currently empty for monitor alerts).
messagestring or nullOptional notification message.
metadataobjectAlert details including view_name, dataset_name, result_count, batch_id, and is_alert.
dataset_idUUID or nullThe dataset associated with this notification.
created_atdatetimeWhen the notification was generated.
read_atdatetime or nullWhen the notification was marked as read. null if unread.

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.
# 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.
GET /api/v1/notifications/unread-count
Authorization: Bearer <jwt>

Query Parameters

ParameterTypeDefaultDescription
dataset_idUUIDnullCount only unread notifications for a specific dataset.

Example

# 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)

{
  "count": 3
}

Mark Notification as Read

Mark a single notification as read by its ID.
POST /api/v1/notifications/{notification_id}/read
Authorization: Bearer <jwt>

Parameters

ParameterLocationTypeRequiredDescription
notification_idpathUUIDYesThe notification to mark as read.

Example

curl -X POST \
  -H "Authorization: Bearer <jwt>" \
  "https://app.visual-layer.com/api/v1/notifications/<notification_id>/read"

Response (200)

{
  "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.
POST /api/v1/notifications/read-all
Authorization: Bearer <jwt>

Query Parameters

ParameterTypeDefaultDescription
dataset_idUUIDnullMark only notifications for this dataset as read. If omitted, all notifications are marked as read.

Example

# 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)

{
  "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.
GET /api/v1/notifications/stream
Authorization: Bearer <jwt>

Query Parameters

ParameterTypeDefaultDescription
dataset_idUUIDnullStream only notifications for a specific dataset.

Event Types

EventDescription
notificationA new notification. The data field contains the full notification JSON object.
pingKeepalive event sent every 3 seconds. No meaningful data.
errorAn internal error occurred. The connection remains open and retries automatically.

JavaScript Example

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

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")
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.

Python Example: Full Workflow

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 for the error response format and Python handling patterns.
HTTP CodeMeaningCommon Cause
200Request successful.
401Unauthorized.Invalid or expired JWT token.
404Not found.Notification does not exist, does not belong to the user, or is already read (for mark-as-read).
422Validation error.Invalid query parameter values (for example, limit exceeding 100).
500Internal server error.Contact support if this persists.

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.

Saved Views API

Create views and configure monitoring and alerting.

Monitoring and Alerts

Understand how monitoring and alerting works across your datasets.

Add Media

Add media to datasets, triggering monitoring evaluation on all views.

Task Manager API

Track media addition tasks that trigger monitoring evaluation.