# Notification Catalog (/docs/notifications/catalog)



Layers does not ship a dotted-code catalog ("ads.wallet.floor\_hit" /
etc.) today. Notifications are free-form rows with a `source_type`
tag, a `title`, a `body`, and an optional `action_config` pointing at
an allow-listed action.

## Notification fields [#notification-fields]

Every notification is a row in the `notifications` table with:

* `scope` — `user`, `project`, or `organization`.
* `user_id` / `project_id` / `organization_id` — depending on scope.
* `type` — `broadcast`, `confirmation`, `selection`, `input`.
* `title`, `body` — plain text for the bell.
* `icon` — optional; one of `bell`, `info`, `alert`, `warning`,
  `success`, `message`, `star`, `heart`, `zap`, `gift`, `calendar`,
  `users`, `settings`. If omitted, a priority-based default is used.
* `priority` — `low`, `normal`, `high`, `urgent`.
* `source_type`, `source_id`, `source_metadata` — free-form tagging
  for traceability. Conventional values include `system`, `workflow`,
  `content`, `billing`, `ugc`.
* `action_config` — required for non-broadcast types; see below.

## Registered actions [#registered-actions]

Interactive notifications can only execute actions in the immutable
`ACTION_REGISTRY`. Current entries:

| `action_key`       | Endpoint                                           | Method | Required params   | Body template                 |
| ------------------ | -------------------------------------------------- | ------ | ----------------- | ----------------------------- |
| `approve_content`  | `/api/content/{content_id}/approve`                | POST   | `content_id`      | —                             |
| `reject_content`   | `/api/content/{content_id}/reject`                 | POST   | `content_id`      | —                             |
| `adjust_spend_cap` | `/api/projects/{project_id}/spend-cap`             | PATCH  | `project_id`      | `{ amount: "{input.value}" }` |
| `acknowledge`      | `/api/notifications/{notification_id}/acknowledge` | POST   | `notification_id` | —                             |

Adding a new action is a code change — the allow-list is enforced to
prevent open-ended side effects from notification responses.

## Fan-out [#fan-out]

When a notification is created, one inbox row is written per recipient.
The bell UI subscribes to inbox updates over a realtime channel, so new
and updated notifications appear without a page refresh.

## Response handling [#response-handling]

For `confirmation` / `selection` / `input`, the frontend posts the
user's answer to Layers' API, which:

1. Validates inbox access and idempotency.
2. Writes the response to `notification_responses`.
3. Executes the registered action (builds the URL from
   `buildActionUrl`, body from `buildActionBody`).
4. Records the outcome on the response row
   (`pending` → `executing` → `completed` or `failed`).
5. Marks the notification read for the responding user.

For the canonical implementation guide, see the internal engineering
doc `docs/notification_system_usage.md`.
