---
title: "Create comment"
method: POST
path: "/api/apps/{app_id}/comments"
---

# Create comment

`POST /api/apps/{app_id}/comments`

<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>

Starts a new comment thread on the app with its first comment.

Set `page_path` to the app page the comment is about. `anchor` pins the thread to an element of the app's preview. The builder fills it in when you click an element, and `source_location` must match that element's `data-source-location` attribute in the preview for a pin to show. Leave `anchor` out to post a thread with no pin. It still shows in the builder's comments panel.

To attach a screenshot, upload the image with [Upload app file](/api-reference/upload-app-file) and `visibility` set to `private`, then pass its `file_uri` as `screenshot_file_uri`. Only private files uploaded to this app are accepted.

`mentioned_emails` records who the comment mentions. Only emails that [List mentionable users](/api-reference/list-mentionable-users) returns are kept, and the rest are dropped without an error. Mentioning someone sends them no email or notification, and responses never return the list.

Comments don't reach the builder agent on their own. It only works on a thread when someone sends the thread to the builder chat from the builder. Anyone with the app open in the builder sees the change right away.

This is limited to 120 requests per minute per app, shared by every other comments endpoint. A signed-in session has its own limit, and every personal access token for the workspace shares one. Some workspaces have a different limit. Every request that counts against the limit gets `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds) headers, and a `429` also gets `Retry-After` in seconds. If the limiter itself is unavailable, requests go through without these headers.

<Note>Call this as an editor of the app, with a personal access token sent as a Bearer token or from a signed-in session. Viewers in the app's workspace, read-only tokens, and workspace API keys are refused.</Note>

<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time.</Warning>

## Path parameters

- `app_id` string, required — ID of the app.

## Request body

- CreateAppCommentPayload — A new comment thread.
  - `content` string, required — Text of the comment, 1 to 5000 characters.
  - `page_path` string — Path of the app page the comment is about. Defaults to `/`.
  - `anchor` CommentAnchor — Where a comment thread is pinned in the app's preview. When the builder's AI edits the anchored file, Base44 moves an open thread's `source_location` to the element's new line.
    - `source_location` string, nullable — Position of the element's tag in the app's code, as `file:line:column`, exactly as the element's `data-source-location` attribute in the preview. The pin shows only on an element with this value. `null` for a thread with no pin.
    - `element_tag` string, nullable — HTML tag of the element, such as `button`.
    - `instance_index` integer, nullable — Which of the elements sharing `source_location` the pin is on, counting from `0` in page order, for an element repeated in a list. `null` means the first.
    - `point` CommentAnchorPoint — Fractions of the anchored element's rect, not of the document.
      - `x` number, required — Horizontal position, as a fraction of the element's width.
      - `y` number, required — Vertical position, as a fraction of the element's height.
    - `crop` CommentAnchorCrop — The screenshot's area in page pixels: the visible area plus the page's scroll when it was taken.
      - `x` number, required — Left edge, in page pixels.
      - `y` number, required — Top edge, in page pixels, scroll included.
      - `width` number, required — Width, in pixels.
      - `height` number, required — Height, in pixels.
    - `region` CommentAnchorRegion — The screenshot's area as fractions of the visible preview when it was taken, so it depends on the scroll.
      - `x` number, required — Left edge, as a fraction of the preview's width.
      - `y` number, required — Top edge, as a fraction of the preview's height.
      - `width` number, required — Width, as a fraction of the preview's width.
      - `height` number, required — Height, as a fraction of the preview's height.
    - `viewport_size` CommentAnchorViewportSize — Size of the preview when the screenshot was taken.
      - `width` integer, required — Width of the preview, in pixels.
      - `height` integer, required — Height of the preview, in pixels.
  - `screenshot_file_uri` string, nullable — `file_uri` of a screenshot uploaded to this app with [Upload app file](/api-reference/upload-app-file) and `visibility` set to `private`. Starts with `mp/private/` followed by the app's ID.
  - `mentioned_emails` string[] — Emails of up to 50 people the comment mentions. Mentions send no notification.

## Response `200`

The new thread with its first comment.

- CommentThreadItem — A comment thread with all its comments.
  - `thread` CommentThread, required — A comment thread's state.
    - `id` string, required — ID of the thread.
    - `page_path` string, required — Path of the app page the thread is on.
    - `anchor` CommentAnchor, required — Where a comment thread is pinned in the app's preview. When the builder's AI edits the anchored file, Base44 moves an open thread's `source_location` to the element's new line.
      - `source_location` string, nullable — Position of the element's tag in the app's code, as `file:line:column`, exactly as the element's `data-source-location` attribute in the preview. The pin shows only on an element with this value. `null` for a thread with no pin.
      - `element_tag` string, nullable — HTML tag of the element, such as `button`.
      - `instance_index` integer, nullable — Which of the elements sharing `source_location` the pin is on, counting from `0` in page order, for an element repeated in a list. `null` means the first.
      - `point` CommentAnchorPoint — Fractions of the anchored element's rect, not of the document.
        - `x` number, required — Horizontal position, as a fraction of the element's width.
        - `y` number, required — Vertical position, as a fraction of the element's height.
      - `crop` CommentAnchorCrop — The screenshot's area in page pixels: the visible area plus the page's scroll when it was taken.
        - `x` number, required — Left edge, in page pixels.
        - `y` number, required — Top edge, in page pixels, scroll included.
        - `width` number, required — Width, in pixels.
        - `height` number, required — Height, in pixels.
      - `region` CommentAnchorRegion — The screenshot's area as fractions of the visible preview when it was taken, so it depends on the scroll.
        - `x` number, required — Left edge, as a fraction of the preview's width.
        - `y` number, required — Top edge, as a fraction of the preview's height.
        - `width` number, required — Width, as a fraction of the preview's width.
        - `height` number, required — Height, as a fraction of the preview's height.
      - `viewport_size` CommentAnchorViewportSize — Size of the preview when the screenshot was taken.
        - `width` integer, required — Width of the preview, in pixels.
        - `height` integer, required — Height of the preview, in pixels.
    - `screenshot_url` string, nullable, required — Signed link to the screenshot attached to the thread, valid for one hour. `null` when the thread has no screenshot or it can't be read.
    - `resolved_at` string, date-time, nullable, required — When the thread was resolved, in UTC, or `null` while it's open.
    - `message_count` integer, required — Number of replies, not counting the first comment.
    - `last_activity_at` string, date-time, nullable, required — When the thread was created or last replied to, in UTC.
    - `created_date` string, date-time, required — When the thread was created, in UTC.
    - `agent_working_since` string, date-time, nullable, required — When the builder agent started working on the thread, in UTC, or `null` when it isn't working on it. A value older than an hour is left over from a turn that stopped.
    - `unread` boolean, required — Whether the thread has a comment from someone else posted after you last read it. Always `false` in the responses of Create comment and Update comment anchor.
  - `comment` CommentMessage, required — One comment: the first comment of a thread or a reply.
    - `id` string, required — ID of the comment.
    - `content` string, required — Text of the comment.
    - `sender_id` string, required — ID of the Base44 user who wrote it, or `base44` for a reply from the builder agent.
    - `sender_name` string, required — Name of the author when they wrote it, or their email when they had no name. `Base44` for the builder agent.
    - `sender_avatar_url` string, nullable, required — URL of the author's profile image, or `null` when they have none.
    - `created_date` string, date-time, required — When the comment was posted, in UTC.
    - `edited_at` string, date-time, nullable, required — When the comment was last edited, in UTC, or `null` when it never was.
    - `reactions` object, required — Each emoji reacted with, mapped to the IDs of the users who reacted with it.
  - `replies` CommentMessage[], required — Replies, oldest first.
    - `id` string, required — ID of the comment.
    - `content` string, required — Text of the comment.
    - `sender_id` string, required — ID of the Base44 user who wrote it, or `base44` for a reply from the builder agent.
    - `sender_name` string, required — Name of the author when they wrote it, or their email when they had no name. `Base44` for the builder agent.
    - `sender_avatar_url` string, nullable, required — URL of the author's profile image, or `null` when they have none.
    - `created_date` string, date-time, required — When the comment was posted, in UTC.
    - `edited_at` string, date-time, nullable, required — When the comment was last edited, in UTC, or `null` when it never was.
    - `reactions` object, required — Each emoji reacted with, mapped to the IDs of the users who reacted with it.
  - `reactor_names` object, required — User ID mapped to name, for the people who reacted anywhere in the thread.

## Other responses

- `400` — `screenshot_file_uri` isn't a private file uploaded to this app.
- `401` — Missing or invalid credentials.
- `403` — You don't have editor access to this app, you're a viewer in the app's workspace, the app is blocked, or your token is read-only or a workspace API key.
- `404` — App not found.
- `409` — Your workspace requires an unlocked SSO session.
- `422` — Validation Error
- `429` — Rate limit exceeded. Wait the number of seconds in `Retry-After` before retrying.

## Changes

> 28 revisions in range; 1 not diffed.

- **2026-10-06** `dddf17e0f9f0` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/adexad/apis/base44-app-management-api/changes/api/apps/:app_id/comments/post.md)

---

[API](https://skmtc.dev/adexad/apis/base44-app-management-api.md) · [All operations](https://skmtc.dev/adexad/apis/base44-app-management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/adexad/apis/base44-app-management-api/revisions/99df85f579f9?raw)
