Skip to content
CommentKit / Reference

API reference

Every operation, generated from the OpenAPI description of /api/v1.

Generated from the OpenAPI description of CommentKit API 1.0.0. Base URL: https://commentkit.dev/api/v1. Read the API overview first for sign-in, retries and limits, and error codes for what each code means.

Security schemes

  • Widget session (http bearer): 15-minute access token from /oauth/token, bound to one project and one website origin.
  • Agent token (http bearer): Project-scoped agent token (read; optional reply, status and automate scopes). Server side only.
  • extensionSession (http bearer): 15-minute access token from /extension/oauth/token; only the extension routes accept it.

Widget config

GET /widget/config

Public project configuration the loader reads before mounting

Cached for 60 seconds, so the kill switch and appearance apply within a minute.

Auth: none (public) · getWidgetConfig

Parameters

NameInTypeNotes
If-None-MatchheaderstringThe ETag of a page you already have. If nothing on it changed, the answer is 304 with no body. Signed file links in a cached page expire after 15 minutes.
keyrequiredquerystringPublishable project key (pk_live_… or pk_test_…). Identifies; grants nothing.

Responses

  • 200 Configuration for this website. WidgetConfig
  • 304 Nothing changed since the ETag in If-None-Match; reuse the page you have. Headers: ETag.
  • 403 origin_not_allowed (website not on the project's list). Problem
  • 404 unknown_project (no project has this key). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /widget/channel

Which SDK version the /cdn/v1/ loader should import (CDN canary)

A stable share of projects (by key) gets a newer pinned version first; null means the stable /cdn/v1/ files. Readable from any origin, cached for 60 seconds.

Auth: none (public) · getWidgetChannel

Parameters

NameInTypeNotes
keyrequiredquerystring

Responses

  • 200 The version to load.
    FieldTypeNotes
    versionrequiredstring | null
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /client-errors

The widget reports its own crash (never host-page errors), tagged with its version

Auth: none (public) · reportClientError

Request body (application/json)

FieldTypeNotes
keyrequiredstring
versionrequiredstring
kind"crash" | "start"
messagestring
stackstring
noncestringThe reporting.nonce from this page's first /widget/config read. Only reports with a valid nonce (or a widget bearer token for this project) count towards a canary rollback; others are accepted and ignored.

Responses

  • 202 Received.
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

Sign-in

POST /oauth/token

Exchange a one-time code (PKCE) or rotate a refresh token

Refresh tokens rotate on every use. Presenting the previous refresh token again within 30 seconds of its rotation (two tabs refreshing at once) returns the same new pair; after that, replaying it revokes the whole session family. When the project turned "Keep reviewers signed in" off, refresh tokens last 12 hours whatever the sign-in asked for.

Auth: none (public) · exchangeToken

Request body (application/json)

FieldTypeNotes
grant_typerequired"authorization_code" | "refresh_token"
client_idrequiredstringThe publishable project key.
codestring
code_verifierstring
refresh_tokenstring

Responses

  • 200 A new token pair. TokenPair
  • 400 invalid_grant (code or refresh token expired, used or wrong). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /oauth/revoke

Sign out (revokes the refresh token family)

Auth: none (public) · revokeSession

Request body (application/json)

FieldTypeNotes
refresh_tokenstring

Responses

  • 204 Signed out. Unknown tokens are accepted silently.
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /guest/start

Guests on a share link - comment first, confirm the emailed code after

The "10-second first comment". Instead of opening the sign-in popup, the widget asks a guest with a share link for their name and email and calls this. We email the usual 6-digit code and return a **pending token** (bearer, bound to this project and website, valid 24 hours). With the pending token the widget may only: POST /threads (once; the thread comes back with pending_verification: true), POST /uploads and POST /uploads/{attachment}/complete (its screenshot and files), POST /guest/verify and POST /guest/resend. Everything else answers 403 verification_required. A held thread is invisible to everyone else: not in lists, counts, the dashboard or search, and no notifications, webhooks or live updates go out. When the guest confirms the code it is published and all of those happen then. Unconfirmed threads are deleted after 24 hours. Browser only: the Origin must be one of the project's websites (never localhost).

Auth: none (public) · startGuestComment

Request body (application/json)

FieldTypeNotes
client_idrequiredstringThe publishable project key.
sharerequiredstringThe share (review) link token the guest arrived with.
namerequiredstring
emailrequiredstring (email)

Responses

  • 201 Code emailed (at most once a minute per address); here is the pending token. PendingGuestToken
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 origin_not_allowed (website not on the project's list). Problem
  • 410 share_expired (the client link expired). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

POST /guest/verify

Confirm the emailed code; held feedback is published and a normal session starts

Send with the pending token from POST /guest/start. On success the guest's held threads are published (notifications and webhooks go out now), the pending token stops working, and you get a normal widget session (same shape as /oauth/token). A wrong or expired code is 422 with errors.code; ask for a new one with POST /guest/resend.

Auth: Widget session · verifyGuestComment

Request body (application/json)

FieldTypeNotes
coderequiredstring

Responses

  • 200 Confirmed.
    FieldTypeNotes
    token_typerequired"Bearer"
    access_tokenrequiredstring
    expires_inrequiredintegerSeconds (15 minutes).
    refresh_tokenrequiredstringRotates on every use.
    published_thread_idsrequired(string (uuid))[]
  • 401 unauthenticated (no valid session). Problem
  • 403 guest_removed (the team removed this guest); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project). Problem
  • 409 not_pending (the session is already confirmed). Problem
  • 410 share_expired (the client link expired). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

POST /guest/resend

Email the code again (pending token; at most once a minute)

Auth: Widget session · resendGuestCode

Responses

  • 202 Sent, unless one went out in the last minute.
    FieldTypeNotes
    statusrequired"sent"
  • 401 unauthenticated (no valid session). Problem
  • 403 origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project). Problem
  • 409 not_pending (the session is already confirmed). Problem
  • 410 share_expired (the client link expired). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

Session

GET /me

Who is signed in, their role and what the UI may offer

Auth: Widget session · getSession

Responses

  • 200 The current session. Session
  • 401 unauthenticated (no valid session). Problem
  • 403 origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

GET /people

People who can be mentioned or assigned (names only, never emails)

Auth: Widget session · listPeople

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 People on the project.
    FieldTypeNotes
    datarequired(Person & object)[]
  • 401 unauthenticated (no valid session). Problem
  • 403 origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /labels

The project's labels

Auth: Widget session · listLabels

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 Labels, sorted by name.
    FieldTypeNotes
    datarequiredLabel[]
  • 401 unauthenticated (no valid session). Problem
  • 403 origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /usage

The project's storage, for team members

Auth: Widget session · getUsage

Responses

  • 200 Storage used and the plan's limit per project (null when unlimited).
    FieldTypeNotes
    storagerequiredobject
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (team members only, for usage); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

GET /branches

Branches with open feedback, most recent first

Auth: Widget session · listBranches

Responses

  • 200 Up to 50 branches.
    FieldTypeNotes
    datarequiredobject[]
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (team members only, for usage); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

GET /link-preview

Title and description of a page linked in a comment

Fetched by CommentKit (public addresses only, no images), cached for a day.

Auth: Widget session · getLinkPreview

Parameters

NameInTypeNotes
urlrequiredquerystring

Responses

  • 200 A preview.
    FieldTypeNotes
    datarequiredobject
  • 204 No preview for this address.
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (team members only, for usage); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

Threads

GET /threads

Feedback on this project (the widget inbox and page pins)

Auth: Widget session · listThreads

Parameters

NameInTypeNotes
page_urlquerystringOnly feedback on this page.
statusquerystringComma-separated statuses, or active (open, in progress, in review; the default) or all.
branchquerystring
environmentqueryEnvironment
assigneequerystringA person id, me or none; up to 10 ids (or me) comma-separated match any of them, among all of a thread's assignees. none (nobody assigned) stands alone: combining it with people is 422.
priorityquerystringComma-separated priorities, e.g. high,urgent.
authorquery"me"me: only feedback you wrote.
qquerystringFull-text search, or a reference such as CK-12.
labelquerystringOnly threads with any of these labels: comma-separated label ids or names (names ignore case).
sortquery"recent" | "oldest" | "priority" | "number"
limitqueryinteger
cursorquerystringnext_cursor from the previous page.
If-None-MatchheaderstringThe ETag of a page you already have. If nothing on it changed, the answer is 304 with no body. Signed file links in a cached page expire after 15 minutes.

Responses

  • 200 One page of threads. Headers: ETag.
    FieldTypeNotes
    datarequiredThread[]
    next_cursorrequiredstring | null
  • 304 Nothing changed since the ETag in If-None-Match; reuse the page you have. Headers: ETag.
  • 401 unauthenticated (no valid session). Problem
  • 403 origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

POST /threads

Leave feedback (the thread and its first message)

Retrying with the same Idempotency-Key within 24 hours replays the first response (Idempotent-Replayed: true). After that, the same key still returns the original thread, with 200. With a guest's pending token (see POST /guest/start) one thread may be created; it is held with pending_verification: true until the guest confirms their email, and a second one is 409 pending_limit.

Auth: Widget session · createThread

Parameters

NameInTypeNotes
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json): CreateThread

Responses

  • 201 Created. ThreadEnvelope Headers: ETag.
  • 200 Already created by an earlier request with this Idempotency-Key. ThreadEnvelope Headers: ETag.
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 409 pending_limit (an unconfirmed guest already has a held comment); idempotency_in_progress (the first request with this key is still running). Problem
  • 422 validation_failed with plain-language messages per field, or idempotency_key_reused when the Idempotency-Key was already used for a different request. ValidationProblem | IdempotencyKeyReused
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

GET /threads/{thread}

One thread with its first message and capture

Auth: Widget session · getThread

Parameters

NameInTypeNotes
threadrequiredpathstring (uuid)

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 The thread. ThreadEnvelope Headers: ETag.
  • 401 unauthenticated (no valid session). Problem
  • 403 origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

PATCH /threads/{thread}

Change status, triage fields or move the pin

Reviewers and guests may only move feedback to resolved (verify) or back to open (reopen). Workspace members may change everything. The person who wrote the feedback may also change its description and style_changes.

Auth: Widget session · updateThread

Parameters

NameInTypeNotes
threadrequiredpathstring (uuid)
If-MatchheaderstringThe thread's ETag. When it no longer matches, the change is refused with 412.

Request body (application/json): UpdateThread

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 The updated thread. ThreadEnvelope Headers: ETag.
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 412 edit_conflict (If-Match no longer matches). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

DELETE /threads/{thread}

Delete feedback (authors while untouched, managers always)

Auth: Widget session · deleteThread

Parameters

NameInTypeNotes
threadrequiredpathstring (uuid)

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 204 Deleted. Files are purged after the grace period.
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /threads/{thread}/checklist

Add a checklist item

Auth: Widget session · addChecklistItem

Parameters

NameInTypeNotes
threadrequiredpathstring (uuid)

Request body (application/json)

FieldTypeNotes
bodyrequiredstring

Responses

  • 201 Added. ChecklistItemEnvelope
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (only the author or the team can change the checklist; or feedback is read-only). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

PATCH /checklist/{item}

Tick, untick or rename a checklist item

Auth: Widget session · updateChecklistItem

Parameters

NameInTypeNotes
itemrequiredpathstring (uuid)

Request body (application/json)

FieldTypeNotes
bodystring
doneboolean

Responses

  • 200 Changed. ChecklistItemEnvelope
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (only the author or the team can change the checklist; or feedback is read-only). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

DELETE /checklist/{item}

Remove a checklist item

Auth: Widget session · deleteChecklistItem

Parameters

NameInTypeNotes
itemrequiredpathstring (uuid)

Responses

  • 204 Removed.
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (only the author or the team can change the checklist; or feedback is read-only). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

Messages

GET /threads/{thread}/messages

The conversation, oldest first (the first item is the feedback itself)

Auth: Widget session · listMessages

Parameters

NameInTypeNotes
threadrequiredpathstring (uuid)
limitqueryinteger
cursorquerystringnext_cursor from the previous page.
If-None-MatchheaderstringThe ETag of a page you already have. If nothing on it changed, the answer is 304 with no body. Signed file links in a cached page expire after 15 minutes.

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 One page of messages. MessagePage Headers: ETag.
  • 304 Nothing changed since the ETag in If-None-Match; reuse the page you have. Headers: ETag.
  • 401 unauthenticated (no valid session). Problem
  • 403 origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /threads/{thread}/messages

Reply (reopens resolved or closed feedback)

Auth: Widget session · createMessage

Parameters

NameInTypeNotes
threadrequiredpathstring (uuid)
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json)

FieldTypeNotes
bodyrequiredBody
reply_to_idstring (uuid) | null
attachment_ids(string (uuid))[]
mention_idsinteger[]

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 201 Created. MessageEnvelope Headers: ETag.
  • 200 Already created by an earlier request with this Idempotency-Key. MessageEnvelope Headers: ETag.
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 409 idempotency_in_progress (the first request with this key is still running). Problem
  • 422 validation_failed with plain-language messages per field, or idempotency_key_reused when the Idempotency-Key was already used for a different request. ValidationProblem | IdempotencyKeyReused
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

PATCH /messages/{message}

Edit your own message

Auth: Widget session · updateMessage

Parameters

NameInTypeNotes
messagerequiredpathstring (uuid)
If-MatchheaderstringThe message's etag (or the ETag of the last write). When it no longer matches, the edit is refused with 412.

Request body (application/json)

FieldTypeNotes
bodyrequiredBody
mention_idsinteger[]Everyone mentioned in the edited text. People not in the list are no longer mentioned; people new to it are notified. Leave it out to keep the mentions as they are.

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 The edited message. MessageEnvelope Headers: ETag.
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 412 edit_conflict (If-Match no longer matches). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

DELETE /messages/{message}

Delete a reply (the first message is deleted with its thread)

Auth: Widget session · deleteMessage

Parameters

NameInTypeNotes
messagerequiredpathstring (uuid)

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 204 Deleted.
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

PUT /messages/{message}/reactions

React (idempotent)

Auth: Widget session · addReaction

Parameters

NameInTypeNotes
messagerequiredpathstring (uuid)

Request body (application/json)

FieldTypeNotes
emojirequiredEmoji

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 204 Reaction set.
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

DELETE /messages/{message}/reactions

Remove your reaction (idempotent)

Auth: Widget session · removeReaction

Parameters

NameInTypeNotes
messagerequiredpathstring (uuid)

Request body (application/json)

FieldTypeNotes
emojirequiredEmoji

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 204 Reaction removed.
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

Uploads

POST /uploads

Reserve an attachment and get a short-lived upload URL

1. POST /uploads → upload.url. 2. PUT the file there with upload.headers. 3. POST /uploads/{id}/complete. The file is checked, re-encoded (images) or scanned, and becomes ready; only ready files are ever served.

Auth: Widget session · createUpload

Parameters

NameInTypeNotes
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json)

FieldTypeNotes
namerequiredstring
mimerequired"image/png" | "image/jpeg" | "image/webp" | "image/gif" | "application/pdf" | "text/plain" | "text/csv" | "video/mp4" | "video/webm" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/vnd.openxmlformats-officedocument.presentationml.presentation"Exactly one of the accepted types (UploadRules::ALLOWED); anything else answers 422.
bytesrequiredinteger
kind"screenshot" | "file"

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 201 Upload reserved. UploadTicket
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature); storage_full (project storage is full). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 409 idempotency_in_progress (the first request with this key is still running). Problem
  • 422 validation_failed with plain-language messages per field, or idempotency_key_reused when the Idempotency-Key was already used for a different request. ValidationProblem | IdempotencyKeyReused
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

PUT /uploads/{attachment}/content

Development-only upload target (production uploads go straight to object storage)

Auth: none (public) · putUploadContent

Parameters

NameInTypeNotes
attachmentrequiredpathstring (uuid)
signaturerequiredquerystring
expiresrequiredquerystring

Request body (application/octet-stream): string

Responses

  • 204 Stored.
  • 403 http_403 (the signed link expired or was changed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 422 size_mismatch (empty or larger than announced). Problem

POST /uploads/{attachment}/complete

Confirm the file arrived; processing starts

Auth: Widget session · completeUpload

Parameters

NameInTypeNotes
attachmentrequiredpathstring (uuid)
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 Already completed earlier. AttachmentEnvelope
  • 202 Received; checking and processing the file. AttachmentEnvelope
  • 401 unauthenticated (no valid session). Problem
  • 402 plan_required (plan has ended or lacks the feature); storage_full (project storage is full). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 409 upload_missing (the file did not arrive in storage); idempotency_in_progress (the first request with this key is still running). Problem
  • 422 size_mismatch (empty or larger than announced); file_rejected (contents do not match the type). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /attachments/{attachment}

Download a ready file through a short-lived signed URL

The URL comes from an attachment's url or thumbnail_url; it expires after 15 minutes.

Auth: none (public) · downloadAttachment

Parameters

NameInTypeNotes
attachmentrequiredpathstring (uuid)
signaturerequiredquerystring
expiresrequiredquerystring
variantquery"thumb"

Responses

  • 200 The file. string
  • 302 Redirect to a 5-minute object-storage URL.
  • 403 http_403 (the signed link expired or was changed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

Inbox

PUT /threads/{thread}/watch

Get notified about this thread

Auth: Widget session · watchThread

Parameters

NameInTypeNotes
threadrequiredpathstring (uuid)

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 Whether the person now gets notifications for the thread.
    FieldTypeNotes
    watchingrequiredboolean
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

DELETE /threads/{thread}/watch

Stop notifications for this thread

Auth: Widget session · muteThread

Parameters

NameInTypeNotes
threadrequiredpathstring (uuid)

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 Whether the person now gets notifications for the thread.
    FieldTypeNotes
    watchingrequiredboolean
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /inbox

The person's notifications on this project (the widget bell)

Auth: Widget session · getInbox

Parameters

NameInTypeNotes
cursorquerystringnext_cursor from the previous page.

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 The latest 50 notifications (then the 50 before them with next_cursor) and the unread count. Inbox
  • 401 unauthenticated (no valid session). Problem
  • 403 origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /inbox/read

Mark notifications read (also cancels their pending emails)

Auth: Widget session · markInboxRead

Parameters

NameInTypeNotes
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json)

FieldTypeNotes
ids(string (uuid))[]
thread_idstring (uuid)
allboolean

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 How many were marked and what is still unread.
    FieldTypeNotes
    markedrequiredinteger
    unreadrequiredinteger
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 409 idempotency_in_progress (the first request with this key is still running). Problem
  • 422 validation_failed with plain-language messages per field, or idempotency_key_reused when the Idempotency-Key was already used for a different request. ValidationProblem | IdempotencyKeyReused
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /realtime/auth

Authorize a realtime channel (Pusher protocol); events carry ids only

Pusher protocol (Reverb or Pusher). Events carry ids only: re-read over HTTP, where every permission check applies. Channels: - presence-ck.project.{id}: everyone who can see the project's feedback, with a widget session for that project. Member data is anonymous (it only counts who is here). Event feedback.changed (schema FeedbackChangedPing) for feedback, never for internal tasks. - private-ck.project.{id}.internal: workspace members of the project only. The same feedback.changed ping for internal tasks. - presence-ck.team.{id}: workspace members of the project only. Member data is {id, name, avatar_url} (who of the team is here). No events. - private-ck.inbox.{user|guest}.{id}: the signed-in person (or guest) only. Event inbox.changed with {unread, project_id?}: unread is the unread count; with project_id it is that project's count (a widget on another project should re-read GET /inbox instead of using it). Fields may be added to any event; ignore unknown ones.

Auth: Widget session · authorizeRealtime

Request body (application/json)

FieldTypeNotes
socket_idrequiredstring
channel_namerequiredstring

Responses

  • 503 widget_disabled (feedback is paused for the project). Problem
  • 200 Channel signature.
    FieldTypeNotes
    authrequiredstring
    channel_datastringJSON-encoded presence member. It carries no name, avatar or email: everyone on the channel, guests included, can read it, so it only counts who is here.
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (the role does not allow this); origin_required (no browser Origin); origin_not_allowed (website not on the project's list); wrong_project (session is for another project); no_access (access was removed); verification_required (guest has not entered the emailed code yet). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

Agent

GET /agent/projects/{project}

The project this token belongs to (keys for setup, links to the dashboard)

Auth: Agent token · agentGetProject

Parameters

NameInTypeNotes
projectrequiredpathstring

Responses

  • 200 The project.
    FieldTypeNotes
    datarequiredobject
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /agent/projects/{project}/tokens

Your agent tokens for this project (terminal sign-in only)

Auth: Agent token · agentListTokens

Parameters

NameInTypeNotes
projectrequiredpathstring

Responses

  • 200 The tokens, without their secrets.
    FieldTypeNotes
    datarequiredAgentToken[]
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /agent/projects/{project}/tokens

Create a token for CI, with at most the permissions of your sign-in (terminal sign-in only)

Auth: Agent token · agentCreateToken

Parameters

NameInTypeNotes
projectrequiredpathstring

Request body (application/json)

FieldTypeNotes
namerequiredstring
scopes("reply" | "status" | "automate")[]automate (Zapier, Make and other automations: create tasks and webhooks) only for workspace owners and admins, and only when this sign-in has it.
daysinteger

Responses

  • 201 Created. The secret is shown only here.
    FieldTypeNotes
    datarequiredAgentToken & object
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

DELETE /agent/projects/{project}/tokens/{token}

Revoke one of your agent tokens for this project (terminal sign-in only)

Auth: Agent token · agentRevokeToken

Parameters

NameInTypeNotes
projectrequiredpathstring
tokenrequiredpathinteger

Responses

  • 204 Revoked.
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /agent/projects/{project}/threads

Feedback and tasks for an AI agent (same filters as GET /threads, plus kind)

Auth: Agent token · agentListThreads

Parameters

NameInTypeNotes
projectrequiredpathstring
kindquery"feedback" | "task" | "all"Website feedback, dashboard tasks, or both (default).
page_urlquerystring
statusquerystring
branchquerystring
environmentqueryEnvironment
assigneequerystringUp to 10 person ids (or me) comma-separated, or none alone.
qquerystring
labelquerystringOnly threads with any of these labels: comma-separated label ids or names (names ignore case).
sortquery"recent" | "oldest" | "priority" | "number"
limitqueryinteger
cursorquerystringnext_cursor from the previous page.
If-None-MatchheaderstringThe ETag of a page you already have. If nothing on it changed, the answer is 304 with no body. Signed file links in a cached page expire after 15 minutes.

Responses

  • 200 One page of threads. Headers: ETag.
    FieldTypeNotes
    datarequiredAgentThread[]
    next_cursorrequiredstring | null
  • 304 Nothing changed since the ETag in If-None-Match; reuse the page you have. Headers: ETag.
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /agent/projects/{project}/threads/{thread}

One thread with full context

Auth: Agent token · agentGetThread

Parameters

NameInTypeNotes
projectrequiredpathstring
threadrequiredpathstringThe thread id, or its reference such as CK-12 (or just 12).

Responses

  • 200 The thread. AgentThreadEnvelope Headers: ETag.
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /agent/projects/{project}/threads/{thread}/messages

The conversation, oldest first

Auth: Agent token · agentListMessages

Parameters

NameInTypeNotes
projectrequiredpathstring
threadrequiredpathstringThe thread id, or its reference such as CK-12 (or just 12).
limitqueryinteger
cursorquerystringnext_cursor from the previous page.
If-None-MatchheaderstringThe ETag of a page you already have. If nothing on it changed, the answer is 304 with no body. Signed file links in a cached page expire after 15 minutes.

Responses

  • 200 One page of messages. MessagePage Headers: ETag.
  • 304 Nothing changed since the ETag in If-None-Match; reuse the page you have. Headers: ETag.
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 422 validation_failed, with plain-language messages per field. ValidationProblem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /agent/projects/{project}/threads/{thread}/messages

Reply as "Name (AI agent)" (token needs the reply scope)

Auth: Agent token · agentReply

Parameters

NameInTypeNotes
projectrequiredpathstring
threadrequiredpathstringThe thread id, or its reference such as CK-12 (or just 12).
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json)

FieldTypeNotes
bodyrequiredBody

Responses

  • 201 Posted (the project does not ask for approval). MessageEnvelope
  • 202 The project asks for approval first, so the write waits for a person. Nothing changed yet; check back with GET /agent/projects/{project}/requests/{id}.
    FieldTypeNotes
    datarequiredAgentRequest
    messagerequiredstring
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 409 idempotency_in_progress (the first request with this key is still running). Problem
  • 422 validation_failed with plain-language messages per field, or idempotency_key_reused when the Idempotency-Key was already used for a different request. ValidationProblem | IdempotencyKeyReused
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

POST /agent/projects/{project}/threads/{thread}/ready-for-review

Ask the reviewer to check the fix (token needs the status scope); agents never resolve

Auth: Agent token · agentReadyForReview

Parameters

NameInTypeNotes
projectrequiredpathstring
threadrequiredpathstringThe thread id, or its reference such as CK-12 (or just 12).
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json)

FieldTypeNotes
notestring | null

Responses

  • 200 The thread, now in review (the project does not ask for approval). AgentThreadEnvelope
  • 202 The project asks for approval first, so the write waits for a person. Nothing changed yet; check back with GET /agent/projects/{project}/requests/{id}.
    FieldTypeNotes
    datarequiredAgentRequest
    messagerequiredstring
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 409 idempotency_in_progress (the first request with this key is still running). Problem
  • 422 validation_failed with plain-language messages per field, or idempotency_key_reused when the Idempotency-Key was already used for a different request. ValidationProblem | IdempotencyKeyReused
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

POST /agent/projects/{project}/threads/{thread}/links

Link a commit or pull request to the task (token needs the status scope)

Auth: Agent token · agentLinkCommit

Parameters

NameInTypeNotes
projectrequiredpathstring
threadrequiredpathstringThe thread id, or its reference such as CK-12 (or just 12).
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json)

FieldTypeNotes
urlstring | nullCommit, pull request or other https URL. Required without sha.
shastring | null
titlestring | null

Responses

  • 201 Linked (adding the same link again returns it).
    FieldTypeNotes
    datarequiredThreadLink
  • 202 The project asks for approval first, so the write waits for a person. Nothing changed yet; check back with GET /agent/projects/{project}/requests/{id}.
    FieldTypeNotes
    datarequiredAgentRequest
    messagerequiredstring
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 409 idempotency_in_progress (the first request with this key is still running). Problem
  • 422 validation_failed with plain-language messages per field, or idempotency_key_reused when the Idempotency-Key was already used for a different request. ValidationProblem | IdempotencyKeyReused
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
  • 503 widget_disabled (feedback is paused for the project). Problem

POST /agent/projects/{project}/tasks

Create a task (token needs the automate scope, for Zapier and other automations)

Auth: Agent token · agentCreateTask

Parameters

NameInTypeNotes
projectrequiredpathstring
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json)

FieldTypeNotes
titlerequiredstring
descriptionstring | null
priority"none" | "low" | "medium" | "high" | "urgent" | null

Responses

  • 201 The new task.
    FieldTypeNotes
    datarequiredAgentThread
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 409 idempotency_in_progress (the first request with this key is still running). Problem
  • 422 validation_failed with plain-language messages per field, or idempotency_key_reused when the Idempotency-Key was already used for a different request. ValidationProblem | IdempotencyKeyReused
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /agent/projects/{project}/hooks

Subscribe a webhook (REST hooks, for Zapier); token needs the automate scope

Deliveries are the same signed webhooks as Project → Connect sends (see webhooks). The signing secret is returned once, in this answer. Only the token that added a hook can remove it, and revoking the token removes its hooks.

Auth: Agent token · agentSubscribeHook

Parameters

NameInTypeNotes
projectrequiredpathstring
Idempotency-KeyheaderstringAny unique string (8–100 of A-Z a-z 0-9 _ - : .), new for every new action. Keys are private to the caller and kept 24 hours. Retrying the same request with the same key replays the first successful response with Idempotent-Replayed: true; nothing runs twice. The same key with a different body or path is 422 idempotency_key_reused; while the first request is still running it is 409 idempotency_in_progress (retry after Retry-After). Failed requests are not stored, so they can be retried with the same key. Malformed keys are ignored.

Request body (application/json)

FieldTypeNotes
urlrequiredstring
eventsrequired("task.created" | "task.updated" | "task.status_changed" | "task.deleted" | "task.restored" | "comment.created")[]
descriptionstring | null

Responses

  • 201 Subscribed.
    FieldTypeNotes
    idrequiredstring
    urlrequiredstring
    eventsrequiredstring[]
    secretrequiredstringSigning secret (whsec_…), shown only here. Verify CommentKit-Signature with it.
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 409 idempotency_in_progress (the first request with this key is still running). Problem
  • 422 too_many_webhooks (the project has 10 webhooks); validation_failed (a message per field). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

DELETE /agent/projects/{project}/hooks/{hook}

Remove a webhook this token added

Auth: Agent token · agentUnsubscribeHook

Parameters

NameInTypeNotes
projectrequiredpathstring
hookrequiredpathstring

Responses

  • 204 Removed.
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 402 plan_required (plan has ended or lacks the feature). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /agent/projects/{project}/requests/{agentRequest}

Check whether a write that waited for approval was approved, declined or expired

Auth: Agent token · agentGetRequest

Parameters

NameInTypeNotes
projectrequiredpathstring
agentRequestrequiredpathstring (uuid)

Responses

  • 200 The request.
    FieldTypeNotes
    datarequiredAgentRequest
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /agent/projects/{project}/threads/{thread}/screenshot

The screenshot the reviewer took, as image bytes (never a shareable link)

Auth: Agent token · agentGetScreenshot

Parameters

NameInTypeNotes
projectrequiredpathstring
threadrequiredpathstringThe thread id, or its reference such as CK-12 (or just 12).
sizequery"thumb"

Responses

  • 200 The image. string
  • 401 invalid_agent_token (missing, expired or other project's token). Problem
  • 403 browser_not_allowed (agent token sent from a web page); scope_missing (the token lacks the reply or status permission); no_access (access was removed). Problem
  • 404 not_found (missing, deleted or not visible); no_screenshot (sent without a screenshot). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

Extension

POST /extension/oauth/token

Browser extension sign-in; exchange a one-time code (PKCE) or rotate a refresh token

The code comes from the /extension/connect window. Extension tokens are only accepted by the /extension/* routes. Refresh tokens rotate; replaying an old one revokes the family.

Auth: none (public) · exchangeExtensionToken

Request body (application/json)

FieldTypeNotes
grant_typerequired"authorization_code" | "refresh_token"
codestring
code_verifierstring
refresh_tokenstring

Responses

  • 200 A new token pair. TokenPair
  • 400 invalid_grant (code or refresh token expired, used or wrong). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /extension/oauth/revoke

Sign the browser extension out

Auth: none (public) · revokeExtensionToken

Request body (application/json)

FieldTypeNotes
refresh_tokenstring

Responses

  • 204 Signed out (also when the token was unknown).
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

GET /extension/projects

Projects the person can view, with their publishable key and allowed websites

Auth: extensionSession · listExtensionProjects

Responses

  • 200 The person and their projects.
    FieldTypeNotes
    personrequiredobject
    datarequiredobject[]
  • 401 unauthenticated (no valid session). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

POST /extension/handoff

Trade the extension session for a one-time widget code for one website

The widget redeems the code at /oauth/token from that website, with its own PKCE verifier.

Auth: extensionSession · extensionHandoff

Request body (application/json)

FieldTypeNotes
project_keyrequiredstring
originrequiredstring
code_challengerequiredstring
code_challenge_methodrequired"S256"

Responses

  • 200 A one-time widget code.
    FieldTypeNotes
    coderequiredstring
    expires_inrequiredinteger
  • 401 unauthenticated (no valid session). Problem
  • 403 forbidden (the role does not allow this); origin_not_allowed (website not on the project's list). Problem
  • 422 validation_failed (a message per field). Problem
  • 429 rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.

Schemas

AgentRequest

FieldTypeNotes
idrequiredstring (uuid)
actionrequired"reply" | "ready_for_review" | "link"
statusrequired"pending" | "approved" | "declined" | "expired"
thread_idrequiredstring (uuid)
decline_reasonrequiredstring | null
resultrequiredobject | null
created_atrequiredNullableDateTime
decided_atrequiredNullableDateTime

AgentThread

A thread as agents see it: website feedback or a dashboard task. Tasks have no page URL, pin or first message.

FieldTypeNotes
idrequiredstring (uuid)
numberrequiredinteger
referencerequiredstringHuman reference, unique per project.
kind"feedback" | "task"Website feedback (left on a page) or a task made in the dashboard. The widget only sees feedback.
titlestringShort title. For website feedback it starts as the first line of the comment; the team can edit it.
descriptionstring | nullText of a dashboard task (tasks only; feedback text is in first_message).
linksThreadLink[]
statusrequiredStatus
priorityrequiredPriority
assigneerequiredPerson | null
due_atrequiredDueDate
labelsLabel[]
pagerequiredobjectWhere the feedback was left. url and path are null for dashboard tasks.
anchorrequiredAnchor | nullWhere on the page the pin is. Null for dashboard tasks.
style_changesStyleChange[]Style changes the reviewer suggested for the anchored element. Empty when none.
breakpointrequiredinteger | null
authorrequiredPerson
message_countrequiredinteger
first_messageMessage | nullThe feedback comment. Null for dashboard tasks (their text is in description).
captureobject | null
last_activity_atrequiredDateTime
resolved_atrequiredNullableDateTime
closed_atrequiredNullableDateTime
created_atrequiredDateTime
updated_atrequiredDateTime
pending_verificationbooleanTrue only in the answer to a guest's own held first comment, before they confirm their email (see POST /guest/start). Nobody else ever sees held threads.

AgentThreadEnvelope

FieldTypeNotes
datarequiredAgentThread

Anchor

Where the feedback is pinned. Several strategies; the widget re-anchors with the best match.

FieldTypeNotes
typerequired"element" | "region" | "text" | "page"
selectorstring | null
xpathstring | null
sourcestring | nulldata-ck-src (file:line:col) from a build plugin. Path characters only: no spaces, quotes, backticks or line breaks.
ck_idstring | nullThe element's data-ck-id. Letters, numbers and _ - . : / only.
fingerprintstring | null
componentstring | nullComponent name read from a dev build (React, Vue, Svelte, Preact) when no build plugin is installed. One line.
shadow_pathstring[] | nullSelectors of the open shadow hosts around the element, outermost first.
framestring | nullSelector of the same-origin iframe that holds the element.
framesstring[] | nullNested same-origin iframes (depth 2 to 4), or an iframe inside a shadow root: one hop per iframe, outermost first. A hop is the iframe's selector in its document, after the selectors of any open shadow hosts around it, joined by " >>> ". shadow_path is then the shadow hosts inside the innermost iframe. When set, frame repeats the first hop if it is a plain selector.
quotestring | nullRequired for text anchors.
offsetobject | null
rectobject | nullRequired for region anchors.
pagerequiredobject

Attachment

FieldTypeNotes
idrequiredstring (uuid)
kindrequired"screenshot" | "file"
namerequiredstring
mimerequiredstring
bytesrequiredinteger
widthrequiredinteger | null
heightrequiredinteger | null
statusrequired"pending" | "processing" | "ready" | "rejected"
urlrequiredstring | nullSigned download URL (15 minutes); only when ready.
thumbnail_urlrequiredstring | null

AttachmentEnvelope

FieldTypeNotes
datarequiredAttachment

Body

Markdown-lite text. Rendered as text, never HTML.

string

ChecklistItem

FieldTypeNotes
idrequiredstring (uuid)
bodyrequiredstring
donerequiredboolean

ConsoleEntry

FieldTypeNotes
levelrequired"error" | "warn"
messagerequiredstring
atstring | null

CreateThread

FieldTypeNotes
pagerequiredobject
breakpointinteger | nullViewport width in CSS pixels.
bodyrequiredBody
anchorrequiredAnchor
captureCaptureInput | null
attachment_ids(string (uuid))[]
mention_idsinteger[]
label_idsinteger[]Workspace members only.
priorityPriorityWorkspace members only.
assignee_idinteger | nullWorkspace members only.
assignee_idsinteger[]Everyone assigned, the first being the primary (assignee_id). Workspace members only.
descriptionstring | nullExtra detail under the comment.
checkliststring[]Checklist items to start with.
style_changesStyleChange[] | nullStyle changes suggested for the anchored element (the widget's "Adjust style" panel). Each property at most once.

DateTime

string (date-time)

Device

FieldTypeNotes
browserstring | null
browser_versionstring | null
osstring | null
os_versionstring | null
type"desktop" | "tablet" | "mobile" | null
localestring | null
color_scheme"light" | "dark" | null
user_agentstring | nullShown to workspace members only; left out for reviewers and guests.

DueDate

A calendar day (YYYY-MM-DD), not an instant: show it as that day in any time zone.

string (date) | null

Emoji

"👍" | "❤️" | "🎉" | "👀" | "✅" | "🙏" | "👎" | "😄" | "😍" | "🤔" | "😮" | "😢" | "🔥" | "🚀" | "💯" | "👏" | "🙌" | "💡" | "⚠️" | "❌" | "⭐" | "🎨" | "🐛" | "✨"

Environment

"development" | "preview" | "staging" | "production"

FeedbackChangedPing

The feedback.changed event on presence-ck.project.{id}. It carries ids only: re-read the thread over HTTP (GET /threads/{thread}), where every permission check applies. The server sends at most one ping per thread per second: the first change at once, later ones in that second folded into one trailing ping (coalesced: true) that names the latest change. No ping is sent for a comment still held for a guest's email check, or when nobody is connected to the project. Changes to internal tasks are never sent on this channel (guests and reviewers are on it); they go to the team-only private-ck.project.{id}.internal. Fields may be added; ignore unknown ones.

FieldTypeNotes
typerequiredstringThe (latest) change, for example thread.created, thread.updated, thread.deleted, thread.restored, message.created, message.updated, message.deleted, reaction.changed.
thread_idrequiredstringThe thread that changed.
message_idrequiredstring | null
event_idrequiredstringUnique per change (the latest one for a coalesced ping).
actorrequiredstring | nulluser:{id} or guest:{id} of whoever made the change; null on a coalesced ping. Kept for older widgets; prefer by.
page_pathrequiredstring | nullPath of the page the thread is on (no query or fragment), null for tasks without a page. A widget that only shows the current page can skip pings for other paths; a view across pages should not.
byrequiredstring | nullOpaque key of the person who made the change, equal to their realtime_key from GET /me. Null when an agent, integration or the system made it, and on coalesced pings. A widget may skip a ping with its own key only for a change it has just made in this tab (it already has the response): the same person may have changed it in another tab or the dashboard.
coalescedrequiredbooleanTrue when this ping stands for several changes within one second.

IdempotencyKeyReused

FieldTypeNotes
typerequiredstring (uri)
titlerequiredstring
statusrequiredinteger
coderequired"idempotency_key_reused"
detailstring
instancestring
errorsobject

Inbox

FieldTypeNotes
unreadrequiredinteger
next_cursorrequiredstring | nullOlder notifications: pass as cursor. Null on the last page.
datarequiredobject[]

Label

FieldTypeNotes
idrequiredinteger
namerequiredstring
colorrequiredstring

MarkupShape

FieldTypeNotes
toolrequired"arrow" | "rect" | "pen" | "blur" | "text" | "line" | "ellipse"
widthinteger | nullStroke width in screenshot pixels.
colorstring | null
pointsnumber[] | null
textstring | null

Message

FieldTypeNotes
idrequiredstring (uuid)
thread_idrequiredstring (uuid)
authorrequiredPerson
bodyrequiredstring
sourcerequired"widget" | "dashboard" | "agent" | "email" | "integration" | "migrated"
reply_toobject | null
attachmentsAttachment[]
reactionsobject[]
etagstringSend as If-Match when editing this message.
edited_atrequiredNullableDateTime
created_atrequiredDateTime

MessageEnvelope

FieldTypeNotes
datarequiredMessage

MessagePage

FieldTypeNotes
datarequiredMessage[]
next_cursorrequiredstring | null

NetworkEntry

FieldTypeNotes
methodrequiredstring
urlrequiredstring
statusrequiredinteger
duration_msinteger | null

NullableDateTime

string (date-time) | null

PendingGuestToken

FieldTypeNotes
token_typerequired"Bearer"
access_tokenrequiredstringPending token: only for POST /threads (once), uploads, /guest/verify and /guest/resend.
expires_inrequiredinteger
pending_sessionrequiredobject

Person

Public shape of a person. Never includes an email address.

FieldTypeNotes
typerequired"user" | "guest" | "agent" | "integration" | "system" | "deleted"
idrequiredstring | null
namerequiredstring
avatar_urlrequiredstring | null

Priority

"none" | "low" | "medium" | "high" | "urgent"

Problem

FieldTypeNotes
typerequiredstring (uri)
titlerequiredstring
statusrequiredinteger
coderequiredstringStable machine-readable code. Every code, with what it means and how to fix it, is listed at https://commentkit.dev/docs/errors (for example validation_failed, unauthenticated, invalid_agent_token, scope_missing, browser_not_allowed, origin_not_allowed, plan_required, storage_full, edit_conflict, rate_limited). New codes may be added.
detailstring
instancestring

Session

FieldTypeNotes
personrequiredPerson
rolerequired"admin" | "member" | "reviewer" | "guest"
emailstring | nullYour own address, for Settings.
dashboard_urlstring | nullThis project in the dashboard; team members only.
repositorystring | nullThe linked GitHub repository as owner/name; team members only. Used to open coding agents in the right clone.
realtime_keyrequiredstringOpaque key that feedback.changed pings carry in by when this person made the change (see FeedbackChangedPing). It differs per project; do not parse it.
projectrequiredobject
environmentrequiredEnvironment
capabilitiesrequiredobjectWhat the UI may offer. The server still checks every action.

Status

"open" | "in_progress" | "in_review" | "resolved" | "closed"

StyleChange

One suggested CSS change. Values are plain CSS values (no quotes, colons, semicolons, braces, url() or expression()); the only functions allowed are colour and arithmetic ones such as rgb(), oklch(), calc() and var().

FieldTypeNotes
propertyrequired"font-size" | "font-weight" | "line-height" | "letter-spacing" | "text-align" | "color" | "background-color" | "border-color" | "border-width" | "border-style" | "border-radius" | "opacity" | "margin" | "padding" | "width" | "height" | "gap" | "display"
fromstring | nullThe value on the page when it was suggested.
torequiredstring | nullThe suggested value; null means remove (unset) the value.

Thread

FieldTypeNotes
idrequiredstring (uuid)
numberrequiredinteger
referencerequiredstringHuman reference, unique per project.
kind"feedback"Always feedback here: the widget never sees tasks made in the dashboard.
titlestringShort title. For website feedback it starts as the first line of the comment; the team can edit it.
descriptionstring | nullExtra detail under the comment (the comment itself is first_message).
assigneesPerson[]Everyone assigned, the primary (assignee) first.
checklistChecklistItem[]
linksThreadLink[]
statusrequiredStatus
priorityrequiredPriority
assigneerequiredPerson | null
due_atrequiredDueDate
labelsLabel[]
pagerequiredobject
anchorrequiredAnchor
style_changesStyleChange[]Style changes the reviewer suggested for the anchored element. Empty when none.
breakpointrequiredinteger | null
authorrequiredPerson
message_countrequiredinteger
first_messageMessage
captureobject | null
last_activity_atrequiredDateTime
resolved_atrequiredNullableDateTime
closed_atrequiredNullableDateTime
created_atrequiredDateTime
updated_atrequiredDateTime
pending_verificationbooleanTrue only in the answer to a guest's own held first comment, before they confirm their email (see POST /guest/start). Nobody else ever sees held threads.
watchingbooleanWhether you get emails about this thread (see PUT and DELETE /threads/{thread}/watch). Only on GET /threads/{thread}.

ThreadEnvelope

FieldTypeNotes
datarequiredThread

TokenPair

FieldTypeNotes
token_typerequired"Bearer"
access_tokenrequiredstring
expires_inrequiredintegerSeconds (15 minutes).
refresh_tokenrequiredstringRotates on every use.

UpdateThread

FieldTypeNotes
statusStatus
priorityPriority
assignee_idinteger | nullnull unassigns everyone.
assignee_idsinteger[]The whole list of assignees, the first being the primary.
descriptionstring | nullThe author or the team.
due_atstring (date) | nullA calendar day, YYYY-MM-DD; null clears it. A date-time from older clients counts as the day it names.
label_idsinteger[]
anchorAnchor
style_changesStyleChange[] | nullThe whole list of suggested style changes, replacing the previous one ([] or null clears it). Same rules as when creating. The author or the team. Recorded in the activity as style_changes_changed with {from, to} (both lists).

UploadTicket

FieldTypeNotes
idrequiredstring (uuid)
uploadrequiredobject
max_bytesrequiredinteger

ValidationProblem

FieldTypeNotes
typerequiredstring (uri)
titlerequiredstring
statusrequiredinteger
coderequired"validation_failed"
detailstring
instancestring
errorsrequiredobject

Viewport

FieldTypeNotes
widthrequiredinteger
heightrequiredinteger
dprnumber | null
zoomnumber | null

WebhookActor

FieldTypeNotes
typerequired"user" | "guest" | "agent" | "integration" | "system"
namerequiredstring | null

WebhookEnvelope

Every webhook is a JSON POST with this envelope. Payloads are versioned by api_version.

FieldTypeNotes
idrequiredstring (uuid)The same for every retry and replay of one event (also the CommentKit-Event-Id header).
typerequiredstring
api_versionrequired"2026-10"
created_atrequiredstring (date-time)
projectrequiredobject
datarequiredobject

WebhookPingData

FieldTypeNotes
messagerequiredstring

WebhookStatusChangedData

FieldTypeNotes
taskrequiredWebhookTask
fromrequiredstring
torequiredstring
actorrequiredWebhookActor

WebhookTask

FieldTypeNotes
idrequiredstring (uuid)
referencerequiredstring
kindrequired"feedback" | "task"
titlerequiredstring
statusrequired"open" | "in_progress" | "in_review" | "resolved" | "closed"
priorityrequired"none" | "low" | "medium" | "high" | "urgent"
assigneerequiredobject | null
due_atrequiredDueDate
labelsrequiredstring[]
page_urlrequiredstring | null
urlrequiredstring | null
created_atrequiredstring (date-time) | null
updated_atrequiredstring (date-time) | null

WebhookTaskDeletedData

FieldTypeNotes
taskrequiredobject
actorrequiredWebhookActor

WebhookTaskUpdatedData

FieldTypeNotes
taskrequiredWebhookTask
changesrequiredobjectOne entry per changed field (status, priority, assignee, due_at, title, description, labels, link, anchor, style_changes), each {from, to}. Text is not kept after the change: for title and link from is null and to is the current value; description, anchor and style_changes give to: true.
actorrequiredWebhookActor

WidgetConfig

FieldTypeNotes
projectrequiredobject
enabledrequiredbooleanfalse when the kill switch is on.
read_onlyrequiredbooleanTrue when new feedback is turned off or the plan ended.
environmentrequiredEnvironment
local_only_for_membersrequiredbooleanTrue on localhost that is not an allowed website.
featuresrequiredobject
appearancerequiredobject
privacyrequiredobject
limitsrequiredobject
authrequiredobject
realtimerequirednull | objectSocket settings when live updates are on; null otherwise.
reportingobjectOnly on the first read of a page (X-CommentKit-Load: 1 and a valid X-CommentKit-SDK version). Send nonce with a crash report to /client-errors; it is single-use and valid for 24 hours.