> For the complete documentation index, see [llms.txt](https://docs.api.intratool.de/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.api.intratool.de/api-reference/infoboard/infoboard-posts.md).

# InfoboardPosts

## Introduction

`InfoboardPosts` are the primary Infoboard content resource. A post can belong to an [InfoboardChannel](/api-reference/infoboard/infoboard-channels.md), target [Users](/api-reference/users.md) or [Departments](/api-reference/departments.md), accept [InfoboardComments](/api-reference/infoboard/infoboard-comments.md), and require explicit reading confirmation.

Post visibility combines Targetables with inherited channel Entity Permissions when a channel is assigned.

The `text` field uses the shared [Rich Text](/introduction/rich-text.md) HTML format.

## Model Definition

**Alias**

`infoboardPost`

**Relations**

| Key                | Relation                                                                        | Type            | Relation Field(s)                                                         |
| ------------------ | ------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------- |
| `user`             | [User](/api-reference/users.md)                                                 | Belongs to      | `user_id`                                                                 |
| `departments`      | [Departments](/api-reference/departments.md)                                    | Belongs to many | `department_infoboard_post`                                               |
| `users`            | [Users](/api-reference/users.md)                                                | Belongs to many | `user_infoboard_post`                                                     |
| `infoboardChannel` | [InfoboardChannel](/api-reference/infoboard/infoboard-channels.md)              | Belongs to      | `infoboard_channel_id`                                                    |
| `seenUsers`        | [InfoboardPostSeenUsers](/api-reference/infoboard/infoboard-post-seen-users.md) | Has many        | `infoboard_post_seen_users.post_id`                                       |
| `readUsers`        | [InfoboardPostReadUsers](/api-reference/infoboard/infoboard-post-read-users.md) | Has many        | `infoboard_post_read_users.post_id`                                       |
| `comments`         | [InfoboardComments](/api-reference/infoboard/infoboard-comments.md)             | Has many        | `infoboard_comments.post_id`                                              |
| `latestComment`    | [InfoboardComment](/api-reference/infoboard/infoboard-comments.md)              | Has one         | `infoboard_comments.post_id`                                              |
| `reactions`        | [Reactions](/api-reference/reactions.md)                                        | Morph many      | `reactions.reactable_type`, `reactions.reactable_id`                      |
| `currentReaction`  | [Reaction](/api-reference/reactions.md)                                         | Morph one       | `reactions.reactable_type`, `reactions.reactable_id`, `reactions.user_id` |
| `tags`             | [Tags](/api-reference/tags/tags.md)                                             | Belongs to many | `tag_taggable.taggable_type`, `tag_taggable.taggable_id`                  |

**Assignment modes**

* `null` - Do not target users or departments. This is unavailable when the assigned channel has `apply_allowed_departments` enabled.
* `any_of` - Target the explicitly assigned departments. Direct user assignment is currently ignored by the create and update endpoints.

**Comment visibility constraint types**

* `department` - Limit comments to the author's department.
* `private` - Limit comments to users with the applicable private-comment access and allowed department context.
* `null` - Do not impose a comment visibility constraint.

**Computed Properties**

* `hash` - Hashed representation of the post `id`.

**Capabilities**

* [Targetables](/introduction/resource-capabilities/targetables.md) - `assign_mode` and `departments` define direct post visibility; only `null` and `any_of` are supported, and direct user targets are currently removed from requests.
* [Entity Permissions](/introduction/resource-capabilities/entity-permissions.md) - Posts inherit the `view` and administration effects of their assigned channel and do not own independent Entity Permissions.
* [URL Context](/introduction/resource-capabilities/url-context.md) - Post URLs resolve to access-checked context information.
* [Translations](/introduction/resource-capabilities/translations.md) - The `title` and `text` fields are translatable.
* [Notifications](/introduction/resource-capabilities/notifications.md) - Publishing and relevant updates can notify eligible targeted users when the parent channel enables notifications.
* [Reactions](/introduction/resource-capabilities/reactions.md) - Posts accept reactions when `reactions_forbidden` is `false`.
* [Seen and Read Tracking](/introduction/resource-capabilities/seen-and-read-tracking.md) - Seen records track visible posts, read records capture explicit confirmations, and `reading_confirmation` tells clients whether a confirmation is required.

## User Can Create Posts

Check whether the authenticated user can create at least one `InfoboardPost` globally or in an accessible channel.

**Definition**

<mark style="color:green;">`GET`</mark> `/api/infoboard/user-can-create-posts`

**Behavior**

* The result accounts for both the global post-creation permission and channel-specific creation grants.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('GET', '/api/infoboard/user-can-create-posts', [
    'headers' => ['Authorization' => "Bearer {accessToken}"]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
{
  "status": "success",
  "data": true
}
```

## Unseen and Unread Statistics

List unseen and unread post counts grouped by channel for the authenticated user.

**Definition**

<mark style="color:green;">`GET`</mark> `/api/infoboard/unseen-unread-posts-statistics`

**Behavior**

* Shared value filters can narrow the posts before counts are calculated.
* Only posts the user may mark as seen or read are counted.
* `infoboard_channel_id` is `null` for posts outside a channel.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('GET', '/api/infoboard/unseen-unread-posts-statistics', [
    'headers' => ['Authorization' => "Bearer {accessToken}"]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
{
  "status": "success",
  "data": [
    {
      "infoboard_channel_id": null,
      "unseen_posts_count": 1,
      "unread_posts_count": 0
    },
    {
      "infoboard_channel_id": 1,
      "unseen_posts_count": 2,
      "unread_posts_count": 1
    }
  ]
}
```

## List

List `InfoboardPosts` visible to the current authenticated user.

**Definition**

<mark style="color:green;">`GET`</mark> `/api/infoboard/posts`

**Behavior**

* Visibility includes owned posts and posts available through Targetables, inherited channel Entity Permissions, or an explicit entity grant.
* Scheduled posts remain hidden until their publication time unless the applicable ownership or scheduling access permits them.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('GET', '/api/infoboard/posts', [
    'headers' => ['Authorization' => "Bearer {accessToken}"]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
[
  {
    "id": 1,
    "user_id": 3,
    "infoboard_channel_id": 1,
    "lang_id": "en-US",
    "title": "Quarterly town hall",
    "text": "<p>The next town hall takes place on Friday.</p>",
    "reading_confirmation": true,
    "reactions_forbidden": false,
    "comments_allowed": true,
    "comment_user_confirmation_required": false,
    "comment_visibility_constraint_type": null,
    "force_comment_visibility_constraint": false,
    "pinned": true,
    "pinned_end": "2026-08-08 09:00:00",
    "planned_publish_at": null,
    "published_at": "2026-08-06 09:30:00",
    "delete_interval": null,
    "assign_mode": "any_of",
    "created_at": "2026-08-06 09:30:00",
    "updated_at": "2026-08-06 09:30:00",
    "deleted_at": null,
    "hash": "wrv2jd6x8g1nmlk4o970mqen"
  },
  {
    "id": 2,
    "user_id": 4,
    "infoboard_channel_id": null,
    "lang_id": "en-US",
    "title": "Office maintenance",
    "text": "<p>The west wing will be closed for maintenance on Monday.</p>",
    "reading_confirmation": false,
    "reactions_forbidden": true,
    "comments_allowed": false,
    "comment_user_confirmation_required": false,
    "comment_visibility_constraint_type": null,
    "force_comment_visibility_constraint": false,
    "pinned": false,
    "pinned_end": null,
    "planned_publish_at": null,
    "published_at": "2026-08-06 10:00:00",
    "delete_interval": null,
    "assign_mode": null,
    "created_at": "2026-08-06 10:00:00",
    "updated_at": "2026-08-06 10:00:00",
    "deleted_at": null,
    "hash": "q6n1vx8m4p2kr9dw5e7z0yla"
  }
]
```

## Count

Count `InfoboardPosts` visible to the current authenticated user.

**Definition**

<mark style="color:green;">`GET`</mark> `/api/infoboard/posts/count`

**Behavior**

* The same visibility, publication, and shared filter rules as the list endpoint apply.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('GET', '/api/infoboard/posts/count', [
    'headers' => ['Authorization' => "Bearer {accessToken}"]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
1
```

## Show

Show one visible `InfoboardPost` by ID or hash.

**Definition**

<mark style="color:green;">`GET`</mark> `/api/infoboard/posts/{infoboardPost}`

**Route Parameters**

| Parameter       | Type                  | Description               |
| --------------- | --------------------- | ------------------------- |
| `infoboardPost` | `integer` \| `string` | InfoboardPost ID or hash. |

**Behavior**

* The same Targetables, inherited Entity Permissions, grants, ownership, and publication rules as the list endpoint apply.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('GET', '/api/infoboard/posts/1', [
    'headers' => ['Authorization' => "Bearer {accessToken}"]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
{
  "id": 1,
  "user_id": 3,
  "infoboard_channel_id": 1,
  "lang_id": "en-US",
  "title": "Quarterly town hall",
  "text": "<p>The next town hall takes place on Friday.</p>",
  "reading_confirmation": true,
  "reactions_forbidden": false,
  "comments_allowed": true,
  "comment_user_confirmation_required": false,
  "comment_visibility_constraint_type": null,
  "force_comment_visibility_constraint": false,
  "pinned": true,
  "pinned_end": "2026-08-08 09:00:00",
  "planned_publish_at": null,
  "published_at": "2026-08-06 09:30:00",
  "delete_interval": null,
  "assign_mode": "any_of",
  "created_at": "2026-08-06 09:30:00",
  "updated_at": "2026-08-06 09:30:00",
  "deleted_at": null,
  "hash": "wrv2jd6x8g1nmlk4o970mqen"
}
```

## Create

Create a new `InfoboardPost`.

**Definition**

<mark style="color:yellow;">`POST`</mark> `/api/infoboard/posts`

**Request Keys**

| Key                                   | Type                 | Default         | Description                                                                                       |
| ------------------------------------- | -------------------- | --------------- | ------------------------------------------------------------------------------------------------- |
| `lang_id`                             | `string`             | system language | Language key for the translatable content.                                                        |
| `infoboard_channel_id`                | `integer` \| `null`  | `null`          | ID of the [InfoboardChannel](/api-reference/infoboard/infoboard-channels.md) containing the post. |
| `title`\*                             | `string`             | -               | Post title.                                                                                       |
| `text`\*                              | `string`             | -               | Post content, which can contain HTML.                                                             |
| `reading_confirmation`                | `boolean`            | `false`         | Whether users must explicitly confirm reading.                                                    |
| `comments_allowed`                    | `boolean`            | `true`          | Whether comments can be created.                                                                  |
| `comment_user_confirmation_required`  | `boolean`            | `false`         | Whether comment creation requires a confirmed non-group user identity.                            |
| `comment_visibility_constraint_type`  | `string` \| `null`   | `null`          | Default [comment visibility constraint](#comment-visibility-constraint-types).                    |
| `force_comment_visibility_constraint` | `boolean`            | `false`         | Whether comments must use the post's configured constraint.                                       |
| `pinned`                              | `boolean`            | `false`         | Whether the post is pinned.                                                                       |
| `pinned_end`                          | `datetime` \| `null` | `null`          | Future date at which pinning ends. Relative dates are accepted.                                   |
| `planned_publish_at`                  | `datetime` \| `null` | `null`          | Future publication date. Relative dates are accepted.                                             |
| `delete_interval`                     | `string` \| `null`   | `null`          | ISO 8601 date interval after which the post can be deleted automatically.                         |
| `assign_mode`                         | `string` \| `null`   | derived         | Supported [assignment mode](#assignment-modes).                                                   |
| `department_ids`                      | `array`              | `[]`            | IDs of departments targeted by the post.                                                          |

Keys with `*` are required.

**Behavior**

* The authenticated user becomes the owner; client-provided `user_id` and `user_ids` values are ignored.
* Supplying departments defaults `assign_mode` to `any_of`; otherwise it defaults to `null`. A channel with `apply_allowed_departments` enabled requires at least one department and removes department IDs the author may not assign.
* Without a future `planned_publish_at` and scheduling permission, the post is published immediately. A valid future value stores `published_at` as `null` until publication. Client-provided `published_at` is normalized by this rule.
* `pinned` and `pinned_end` apply only with pin permission. Otherwise `pinned` becomes `false` and `pinned_end` becomes `null`.
* `delete_interval` applies only when the user may delete the post; otherwise it becomes `null`.
* Creating a published post can trigger notifications for eligible targeted users.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('POST', '/api/infoboard/posts', [
    'headers' => ['Authorization' => "Bearer {accessToken}"],
    'json' => [
        'infoboard_channel_id' => 1,
        'title' => 'Quarterly town hall',
        'text' => '<p>The next town hall takes place on Friday.</p>',
        'reading_confirmation' => true,
        'comments_allowed' => true,
        'department_ids' => [1, 2]
    ]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
{
  "status": "success",
  "data": {
    "id": 1,
    "user_id": 3,
    "infoboard_channel_id": 1,
    "lang_id": "en-US",
    "title": "Quarterly town hall",
    "text": "<p>The next town hall takes place on Friday.</p>",
    "reading_confirmation": true,
    "reactions_forbidden": false,
    "comments_allowed": true,
    "comment_user_confirmation_required": false,
    "comment_visibility_constraint_type": null,
    "force_comment_visibility_constraint": false,
    "pinned": false,
    "pinned_end": null,
    "planned_publish_at": null,
    "published_at": "2026-08-06 09:30:00",
    "delete_interval": null,
    "assign_mode": "any_of",
    "created_at": "2026-08-06 09:30:00",
    "updated_at": "2026-08-06 09:30:00",
    "deleted_at": null,
    "hash": "wrv2jd6x8g1nmlk4o970mqen"
  }
}
```

## Reset Relations

Reset selected dependent relations of an `InfoboardPost`.

**Definition**

<mark style="color:yellow;">`POST`</mark> `/api/infoboard/posts/{infoboardPost}/reset`

**Route Parameters**

| Parameter       | Type                  | Description               |
| --------------- | --------------------- | ------------------------- |
| `infoboardPost` | `integer` \| `string` | InfoboardPost ID or hash. |

**Request Keys**

| Key                   | Type      | Default | Description                                               |
| --------------------- | --------- | ------- | --------------------------------------------------------- |
| `seenUsers`           | `boolean` | `false` | Delete seen-user records when `true`.                     |
| `readUsers`           | `boolean` | `false` | Delete read-user records when `true`.                     |
| `comments`            | `boolean` | `false` | Delete comments when `true`.                              |
| `reactions`           | `boolean` | `false` | Delete reactions when `true`.                             |
| `targetedSurveyVotes` | `boolean` | `false` | Delete votes from surveys targeting the post when `true`. |

**Behavior**

* At least one resettable relation must be set to `true`; otherwise the operation returns an error response.
* Relation-specific delete behavior runs for each selected relation, including comment notification cleanup.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('POST', '/api/infoboard/posts/1/reset', [
    'headers' => ['Authorization' => "Bearer {accessToken}"],
    'json' => [
        'seenUsers' => true,
        'readUsers' => true
    ]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
{
  "status": "success",
  "data": {
    "id": 1,
    "user_id": 3,
    "infoboard_channel_id": 1,
    "lang_id": "en-US",
    "title": "Quarterly town hall",
    "text": "<p>The next town hall takes place on Friday.</p>",
    "reading_confirmation": true,
    "reactions_forbidden": false,
    "comments_allowed": true,
    "comment_user_confirmation_required": false,
    "comment_visibility_constraint_type": null,
    "force_comment_visibility_constraint": false,
    "pinned": false,
    "pinned_end": null,
    "planned_publish_at": null,
    "published_at": "2026-08-06 09:30:00",
    "delete_interval": null,
    "assign_mode": "any_of",
    "created_at": "2026-08-06 09:30:00",
    "updated_at": "2026-08-06 09:30:00",
    "deleted_at": null,
    "hash": "wrv2jd6x8g1nmlk4o970mqen"
  }
}
```

## Update

Update an existing `InfoboardPost`.

**Definition**

<mark style="color:blue;">`PUT`</mark> `/api/infoboard/posts/{infoboardPost}`

**Route Parameters**

| Parameter       | Type                  | Description               |
| --------------- | --------------------- | ------------------------- |
| `infoboardPost` | `integer` \| `string` | InfoboardPost ID or hash. |

**Request Keys**

| Key                                   | Type                 | Description                                                                                       |
| ------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------- |
| `lang_id`                             | `string`             | Language key for the translatable content.                                                        |
| `infoboard_channel_id`                | `integer` \| `null`  | ID of the [InfoboardChannel](/api-reference/infoboard/infoboard-channels.md) containing the post. |
| `title`                               | `string`             | Post title.                                                                                       |
| `text`                                | `string`             | Post content, which can contain HTML.                                                             |
| `reading_confirmation`                | `boolean`            | Whether users must explicitly confirm reading.                                                    |
| `comments_allowed`                    | `boolean`            | Whether comments can be created.                                                                  |
| `comment_user_confirmation_required`  | `boolean`            | Whether comment creation requires a confirmed non-group user identity.                            |
| `comment_visibility_constraint_type`  | `string` \| `null`   | Default [comment visibility constraint](#comment-visibility-constraint-types).                    |
| `force_comment_visibility_constraint` | `boolean`            | Whether comments must use the post's configured constraint.                                       |
| `pinned`                              | `boolean`            | Whether the post is pinned.                                                                       |
| `pinned_end`                          | `datetime` \| `null` | Future date at which pinning ends. Relative dates are accepted.                                   |
| `planned_publish_at`                  | `datetime` \| `null` | Future publication date. Relative dates are accepted.                                             |
| `delete_interval`                     | `string` \| `null`   | ISO 8601 date interval after which the post can be deleted automatically.                         |
| `assign_mode`                         | `string` \| `null`   | Supported [assignment mode](#assignment-modes).                                                   |
| `department_ids`                      | `array` \| `null`    | IDs of departments targeted by the post.                                                          |

**Behavior**

* A client-provided `user_id` or `user_ids` value is ignored. Changing `infoboard_channel_id`, `title`, or `text` makes the authenticated user the owner.
* Changing `title` or `text` removes stored translations for the changed source fields.
* Changing the channel without `department_ids` clears department targets. Any channel change also resets seen users, read users, comments, reactions, and targeted survey votes before new departments are synchronized.
* Department assignment follows the same `null` or `any_of` rules and allowed-department filtering as creation.
* Changing `planned_publish_at` to a valid future value schedules the post and clears `published_at`; changing it to `null`, now, or the past publishes immediately. A published post cannot be republished before its existing `published_at` value.
* Pin fields are ignored without pin permission, and `delete_interval` is ignored without delete permission.
* Relevant content, publication, and Targetable changes can trigger updated-post notifications.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('PUT', '/api/infoboard/posts/1', [
    'headers' => ['Authorization' => "Bearer {accessToken}"],
    'json' => [
        'title' => 'Quarterly town hall and Q&A',
        'text' => '<p>The town hall and Q&A take place on Friday.</p>',
        'pinned' => true,
        'pinned_end' => '2026-08-08 09:00:00'
    ]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
{
  "status": "success",
  "data": {
    "id": 1,
    "user_id": 3,
    "infoboard_channel_id": 1,
    "lang_id": "en-US",
    "title": "Quarterly town hall and Q&A",
    "text": "<p>The town hall and Q&A take place on Friday.</p>",
    "reading_confirmation": true,
    "reactions_forbidden": false,
    "comments_allowed": true,
    "comment_user_confirmation_required": false,
    "comment_visibility_constraint_type": null,
    "force_comment_visibility_constraint": false,
    "pinned": true,
    "pinned_end": "2026-08-08 09:00:00",
    "planned_publish_at": null,
    "published_at": "2026-08-06 09:30:00",
    "delete_interval": null,
    "assign_mode": "any_of",
    "created_at": "2026-08-06 09:30:00",
    "updated_at": "2026-08-06 10:30:00",
    "deleted_at": null,
    "hash": "wrv2jd6x8g1nmlk4o970mqen"
  }
}
```

## Delete

Delete an existing `InfoboardPost`.

**Definition**

<mark style="color:red;">`DELETE`</mark> `/api/infoboard/posts/{infoboardPost}`

**Route Parameters**

| Parameter       | Type                  | Description               |
| --------------- | --------------------- | ------------------------- |
| `infoboardPost` | `integer` \| `string` | InfoboardPost ID or hash. |

**Behavior**

* The post is soft-deleted.
* Notifications targeting the post are deleted, and every related comment runs its normal deletion lifecycle.

**Example Request**

{% tabs %}
{% tab title="PHP" %}

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://{tenant}.intratool.de']);
$response = $client->request('DELETE', '/api/infoboard/posts/1', [
    'headers' => ['Authorization' => "Bearer {accessToken}"]
]);
```

{% endtab %}
{% endtabs %}

**Example Response**

```json
{
  "status": "success",
  "data": null
}
```
