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

# Inbox and notifications

> Read and manage HQ's two in-app surfaces: your personal inbox and the workspace notification center.

HQ has **two distinct in-app surfaces**, and they are easy to confuse:

* **Inbox** — your *personal* feed of agent-delivered messages (for example, scheduled-task digests). It is scoped to you alone; admins never see another user's inbox. Inbox messages carry rich content and can be deleted.
* **Notifications** — the *workspace* notification center: async results a tenant should see (recommendations, profile-ready, setup nudges, billing). Items can be addressed to you or be workspace-wide (visible to any member), and they are dismissed rather than deleted.

Both are read with a `Bearer` token and live under `/v1/api`.

<Note>Every request authenticates with `Authorization: Bearer hq_pat_...`. All nine endpoints below accept either a personal access token (`bearer_pat`) or an `oauth2` token.</Note>

## What each item carries

The two list payloads are shaped differently because the surfaces solve different problems.

An **inbox message** ([List inbox](/api-reference/notifications/list-inbox)) carries `id`, `source_kind`, `schedule_id`, `schedule_name`, `title`, `body_markdown`, `ui_strings`, `delivered_at`, and `read_at`. The body is Markdown, and when the message came from a schedule fire it includes the originating schedule's id and name so the UI can group and link. Read state is a **timestamp**: `read_at` is `null` while unread.

A **notification** ([List notifications](/api-reference/notifications/list-notifications)) carries `id`, `kind`, `title`, `body` (optional), `cta` (optional), `severity`, `status`, and `created_at`. The body is optional plain text, there is an optional call-to-action (`cta`), and read state is an explicit `status` **string** (`unread`, `read`, or `dismissed`).

<Note>Read state differs between surfaces. The inbox tracks a `read_at` timestamp (`null` = unread). Notifications track a `status` string. That is why the inbox supports re-marking a message *unread*, but notifications do not.</Note>

## The inbox

<Steps>
  <Step title="List your inbox">
    `GET /v1/api/inbox` returns one page of your messages, newest delivery first.

    ```bash theme={null}
    curl https://api.hq.zone/v1/api/inbox \
      -H "Authorization: Bearer hq_pat_..."
    # → { "messages": [ ... ], "unread_count": 3, "total": 42, "page": 1, "page_size": 25 }
    ```

    The response is a `ListResp`: `messages` (the page), `unread_count` (unread across your *whole* inbox — drives the nav badge, independent of the current page/filter), `total` (rows matching the filter before paging), `page`, and `page_size`.

    Supported query params:

    * `unread=true` — only messages whose `read_at` is null.
    * `q=<text>` — case-insensitive substring search over the title, body, and (for schedule digests) the schedule name.
    * `page=<n>` — 1-based page index, clamped to `>= 1`.
    * `page_size=<n>` — rows per page, clamped to `1..=100`, defaults to `25`.
    * `sort=recent` (default, newest first) or `sort=oldest`.

    ```bash theme={null}
    # Unread only, search "revenue", second page of 50, oldest first
    curl -G https://api.hq.zone/v1/api/inbox \
      -H "Authorization: Bearer hq_pat_..." \
      --data-urlencode "unread=true" \
      --data-urlencode "q=revenue" \
      --data-urlencode "page=2" \
      --data-urlencode "page_size=50" \
      --data-urlencode "sort=oldest"
    ```

    <Tip>A legacy `limit=<n>` param is still honored as the `page_size` fallback for older clients. Prefer `page_size` in new code.</Tip>
  </Step>

  <Step title="Mark one message read or unread">
    Reading an inbox message just sets (or clears) its `read_at` timestamp.

    ```bash theme={null}
    # Mark read — preserves the original read_at if already read
    curl -X POST https://api.hq.zone/v1/api/inbox/<MESSAGE_ID>/read \
      -H "Authorization: Bearer hq_pat_..."
    # → { "ok": true }

    # Mark unread — clears read_at back to null
    curl -X POST https://api.hq.zone/v1/api/inbox/<MESSAGE_ID>/unread \
      -H "Authorization: Bearer hq_pat_..."
    # → { "ok": true }
    ```

    Both return `{ "ok": true }` ([mark read](/api-reference/notifications/mark-inbox-read), [mark unread](/api-reference/notifications/mark-inbox-unread)). Because the lookup is scoped to your own inbox, a message that does not exist — or is not yours — returns `404`.
  </Step>

  <Step title="Mark the whole inbox read">
    `POST /v1/api/inbox/read_all` marks all of your unread messages read. It is idempotent — it returns ok even when nothing was unread.

    ```bash theme={null}
    curl -X POST https://api.hq.zone/v1/api/inbox/read_all \
      -H "Authorization: Bearer hq_pat_..."
    # → { "ok": true }
    ```

    See [Mark all inbox read](/api-reference/notifications/mark-all-inbox-read).
  </Step>

  <Step title="Delete a message">
    The inbox is the only surface where items are **permanently deleted**.

    ```bash theme={null}
    curl -X DELETE https://api.hq.zone/v1/api/inbox/<MESSAGE_ID> \
      -H "Authorization: Bearer hq_pat_..."
    # → { "ok": true }
    ```

    Returns `{ "ok": true }`, or `404` if the message does not exist or is not yours. See [Delete inbox message](/api-reference/notifications/delete-inbox).
  </Step>
</Steps>

## The notification center

<Steps>
  <Step title="List notifications">
    `GET /v1/api/notifications` returns up to 100 non-dismissed notifications, newest first. There are **no query params** — no filters, no pagination.

    ```bash theme={null}
    curl https://api.hq.zone/v1/api/notifications \
      -H "Authorization: Bearer hq_pat_..."
    # → { "items": [ ... ], "unread": 2 }
    ```

    The response is a `NotificationsResp`: `items` and `unread` (how many of the returned-scope rows are still unread — drives the bell badge). The scope includes notifications addressed to you *and* workspace-wide ones visible to any member. Dismissed notifications are excluded. See [List notifications](/api-reference/notifications/list-notifications).
  </Step>

  <Step title="Mark one notification read">
    `POST /v1/api/notifications/{id}/read` flips an `unread` notification to `read` and records the read time.

    ```bash theme={null}
    curl -X POST https://api.hq.zone/v1/api/notifications/<NOTIFICATION_ID>/read \
      -H "Authorization: Bearer hq_pat_..."
    # → { "ok": true }
    ```

    It only affects a notification currently in the `unread` state and always returns `{ "ok": true }`. See [Mark notification read](/api-reference/notifications/mark-notification-read).
  </Step>

  <Step title="Mark all notifications read">
    `POST /v1/api/notifications/read-all` marks all of your unread notifications read and returns the count.

    ```bash theme={null}
    curl -X POST https://api.hq.zone/v1/api/notifications/read-all \
      -H "Authorization: Bearer hq_pat_..."
    # → { "updated": 2 }
    ```

    Unlike the inbox's `read_all` (which returns `ok`), this returns `{ "updated": <n> }` — the number of rows updated. See [Mark all notifications read](/api-reference/notifications/mark-all-notifications-read).
  </Step>

  <Step title="Dismiss a notification">
    Notifications are not deleted; they are **dismissed**. `POST /v1/api/notifications/{id}/dismiss` sets `status` to `dismissed`, records the dismiss time, and removes it from future list responses.

    ```bash theme={null}
    curl -X POST https://api.hq.zone/v1/api/notifications/<NOTIFICATION_ID>/dismiss \
      -H "Authorization: Bearer hq_pat_..."
    # → { "ok": true }
    ```

    Always returns `{ "ok": true }`. See [Dismiss notification](/api-reference/notifications/dismiss-notification).

    <Note>There is no "un-dismiss" and no inbox-style unread toggle for notifications. Dismissing is one-way: the item drops out of the list permanently.</Note>
  </Step>
</Steps>

## Key differences at a glance

* **Scope.** Inbox is per-user (`owner_user_id`); notifications are per-workspace and may be addressed to you or visible to every member.
* **Read state.** Inbox uses a `read_at` timestamp and supports re-marking *unread*; notifications use a `status` string and do not.
* **Removal.** Inbox messages are `DELETE`d; notifications are dismissed (and disappear from the list, but are not re-markable).
* **Listing.** The inbox supports unread filtering, search, pagination, and sort; the notification list takes no params and caps at 100 newest non-dismissed items.
* **Badges.** Inbox `unread_count` is whole-inbox; notification `unread` is over the returned scope.
* **Mark-all response.** Inbox `read_all` returns `{ "ok": true }`; notifications `read-all` returns `{ "updated": <n> }`.
