Sign in Get started

Notifications

The organization event feed - poll for training complete, export ready, and batch job events instead of tracking every run id.

View as Markdown

Notifications are the organization’s event feed. Long-running work emits one when it finishes - training complete, export ready, batch auto-annotation done - so an agent can poll a single feed instead of holding on to every run id it ever started.

The feed is scoped to the API key’s organization. An API key has no user identity of its own, so list returns the whole organization’s feed and mark_all_read clears all of it, rather than one person’s inbox.

Every notification carries a type slug, a human-readable title and message, and a metadata object whose keys depend on the type - a training event carries the run id, an export event carries the export id. Branch on type, then read metadata.

list

One page of notifications, newest first.

Source: Notifications.list

Arg Type Default Notes
unread_only bool False Skip notifications already marked read
limit int 50 Page size, maximum 100
offset int 0 Offset for manual paging
for notification in client.notifications.list(unread_only=True, limit=25):
    print(notification.type, notification.title)
    if notification.type == "training_complete":
        print(notification.metadata)
    client.notifications.mark_read(
        notification_id=notification.id,
    )
pictograph notifications list --unread --limit 25
curl -s "https://api.pictograph.io/api/v1/developer/notifications?unread_only=true&limit=25&offset=0" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns list[Notification]

Notification · 9 fields
class Notification(BaseModel):
    """A single row from the `notifications` table."""
    id: str
    organization_id: str
    user_id: str | None = None
    type: str
    title: str
    message: str | None = None
    metadata: dict[str, Any] | None = None
    read: bool = False
    created_at: datetime

The REST response wraps that list alongside the current unread_count and the limit / offset you sent.

unread_count

Just the number, without fetching the rows. Cheap enough to poll on a short interval.

Source: Notifications.unread_count

pending = client.notifications.unread_count()
if pending:
    print(pending, "new events")
pictograph notifications unread-count
curl -s "https://api.pictograph.io/api/v1/developer/notifications/unread-count" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns int

mark_read

Acknowledge one notification. Idempotent, so re-marking a read notification is harmless.

Source: Notifications.mark_read

Arg Type Default Notes
notification_id str required Notification id from list
client.notifications.mark_read(
    notification_id="1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94",
)
pictograph notifications read 1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94
curl -s -X POST "https://api.pictograph.io/api/v1/developer/notifications/1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94/read" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns None

An id belonging to another organization returns 404 rather than 403, so the feed cannot be used to probe what exists elsewhere.

mark_all_read

Clear the whole organization feed in one call, and get back how many rows that actually changed.

Source: Notifications.mark_all_read

marked = client.notifications.mark_all_read()
print(marked, "notifications marked read")
pictograph notifications read-all
curl -s -X POST "https://api.pictograph.io/api/v1/developer/notifications/read-all" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns int

Idempotent: a second call returns 0.

delete

Remove a notification from the feed permanently. Marking read is usually what you want; delete is for pruning noise you never want to see again.

Source: Notifications.delete

Arg Type Default Notes
notification_id str required Notification id
client.notifications.delete(
    notification_id="1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94",
)
pictograph notifications delete 1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94
curl -s -X DELETE "https://api.pictograph.io/api/v1/developer/notifications/1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94" \
  -H "X-API-Key: $PICTOGRAPH_API_KEY"

Returns None

Polling pattern

Poll the count, and only fetch rows when it moves. The feed is the cheapest way to follow work an agent started, because one request covers every job type at once.

import time

while True:
    if client.notifications.unread_count():
        for notification in client.notifications.list(unread_only=True):
            print(notification.type, notification.title, notification.metadata)
            client.notifications.mark_read(
                notification_id=notification.id,
            )
    time.sleep(30)

For push delivery instead of polling, register a webhook endpoint.

Common errors

Status Exception Cause
404 NotFoundError The notification does not exist in your organization
429 RateLimitError Polling faster than your tier’s request budget allows
Copied to clipboard