---
title: Notifications
description: The organization event feed - poll for training complete, export ready, and batch job events instead of tracking every run id.
section: API Reference
order: 17.5
---
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`](https://github.com/pictograph-io/pictograph-sdk/blob/v1.69.67/src/pictograph/resources/notifications.py)

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

```python
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,
    )
```

```bash
pictograph notifications list --unread --limit 25
```

```bash
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]`

<details>
<summary><code>Notification</code> &middot; 9 fields</summary>

```python
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
```

</details>

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`](https://github.com/pictograph-io/pictograph-sdk/blob/v1.69.67/src/pictograph/resources/notifications.py)

```python
pending = client.notifications.unread_count()
if pending:
    print(pending, "new events")
```

```bash
pictograph notifications unread-count
```

```bash
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`](https://github.com/pictograph-io/pictograph-sdk/blob/v1.69.67/src/pictograph/resources/notifications.py)

| Arg | Type | Default | Notes |
|---|---|---|---|
| `notification_id` | `str` | required | Notification id from `list` |

```python
client.notifications.mark_read(
    notification_id="1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94",
)
```

```bash
pictograph notifications read 1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94
```

```bash
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`](https://github.com/pictograph-io/pictograph-sdk/blob/v1.69.67/src/pictograph/resources/notifications.py)

```python
marked = client.notifications.mark_all_read()
print(marked, "notifications marked read")
```

```bash
pictograph notifications read-all
```

```bash
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`](https://github.com/pictograph-io/pictograph-sdk/blob/v1.69.67/src/pictograph/resources/notifications.py)

| Arg | Type | Default | Notes |
|---|---|---|---|
| `notification_id` | `str` | required | Notification id |

```python
client.notifications.delete(
    notification_id="1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94",
)
```

```bash
pictograph notifications delete 1a6e4f28-9b30-4c57-8d21-6f3b0a5e7c94
```

```bash
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.

```python
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](/docs/api-reference/webhooks.md).

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