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
| Name | In | Type | Notes |
|---|
If-None-Match | header | string | The 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. |
keyrequired | query | string | Publishable 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
| Name | In | Type | Notes |
|---|
keyrequired | query | string | |
Responses
- 200 The version to load.
| Field | Type | Notes |
|---|
versionrequired | string | 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)
| Field | Type | Notes |
|---|
keyrequired | string | |
versionrequired | string | |
kind | "crash" | "start" | |
message | string | |
stack | string | |
nonce | string | The 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)
| Field | Type | Notes |
|---|
grant_typerequired | "authorization_code" | "refresh_token" | |
client_idrequired | string | The publishable project key. |
code | string | |
code_verifier | string | |
refresh_token | string | |
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)
| Field | Type | Notes |
|---|
refresh_token | string | |
Responses
- 204 Signed out. Unknown tokens are accepted silently.
- 429
rate_limited. Wait for Retry-After seconds. Problem Headers: Retry-After.
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.
| Field | Type | Notes |
|---|
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.
| Field | Type | Notes |
|---|
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.
| Field | Type | Notes |
|---|
datarequired | Label[] | |
- 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).
| Field | Type | Notes |
|---|
storagerequired | object | |
- 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.
| Field | Type | Notes |
|---|
datarequired | object[] | |
- 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
| Name | In | Type | Notes |
|---|
urlrequired | query | string | |
Responses
- 200 A preview.
| Field | Type | Notes |
|---|
datarequired | object | |
- 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
| Name | In | Type | Notes |
|---|
page_url | query | string | Only feedback on this page. |
status | query | string | Comma-separated statuses, or active (open, in progress, in review; the default) or all. |
branch | query | string | |
environment | query | Environment | |
assignee | query | string | A 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. |
priority | query | string | Comma-separated priorities, e.g. high,urgent. |
author | query | "me" | me: only feedback you wrote. |
q | query | string | Full-text search, or a reference such as CK-12. |
label | query | string | Only threads with any of these labels: comma-separated label ids or names (names ignore case). |
sort | query | "recent" | "oldest" | "priority" | "number" | |
limit | query | integer | |
cursor | query | string | next_cursor from the previous page. |
If-None-Match | header | string | The 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.| Field | Type | Notes |
|---|
datarequired | Thread[] | |
next_cursorrequired | string | 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
| Name | In | Type | Notes |
|---|
Idempotency-Key | header | string | Any 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
| Name | In | Type | Notes |
|---|
threadrequired | path | string (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
| Name | In | Type | Notes |
|---|
threadrequired | path | string (uuid) | |
If-Match | header | string | The 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
| Name | In | Type | Notes |
|---|
threadrequired | path | string (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
| Name | In | Type | Notes |
|---|
threadrequired | path | string (uuid) | |
Request body (application/json)
| Field | Type | Notes |
|---|
bodyrequired | string | |
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
| Name | In | Type | Notes |
|---|
itemrequired | path | string (uuid) | |
Request body (application/json)
| Field | Type | Notes |
|---|
body | string | |
done | boolean | |
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
| Name | In | Type | Notes |
|---|
itemrequired | path | string (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
| Name | In | Type | Notes |
|---|
threadrequired | path | string (uuid) | |
limit | query | integer | |
cursor | query | string | next_cursor from the previous page. |
If-None-Match | header | string | The 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
| Name | In | Type | Notes |
|---|
threadrequired | path | string (uuid) | |
Idempotency-Key | header | string | Any 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)
| Field | Type | Notes |
|---|
bodyrequired | Body | |
reply_to_id | string (uuid) | null | |
attachment_ids | (string (uuid))[] | |
mention_ids | integer[] | |
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
| Name | In | Type | Notes |
|---|
messagerequired | path | string (uuid) | |
If-Match | header | string | The 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)
| Field | Type | Notes |
|---|
bodyrequired | Body | |
mention_ids | integer[] | 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
| Name | In | Type | Notes |
|---|
messagerequired | path | string (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
| Name | In | Type | Notes |
|---|
messagerequired | path | string (uuid) | |
Request body (application/json)
| Field | Type | Notes |
|---|
emojirequired | Emoji | |
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
| Name | In | Type | Notes |
|---|
messagerequired | path | string (uuid) | |
Request body (application/json)
| Field | Type | Notes |
|---|
emojirequired | Emoji | |
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
| Name | In | Type | Notes |
|---|
Idempotency-Key | header | string | Any 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)
| Field | Type | Notes |
|---|
namerequired | string | |
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. |
bytesrequired | integer | |
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
| Name | In | Type | Notes |
|---|
attachmentrequired | path | string (uuid) | |
signaturerequired | query | string | |
expiresrequired | query | string | |
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
| Name | In | Type | Notes |
|---|
attachmentrequired | path | string (uuid) | |
Idempotency-Key | header | string | Any 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
| Name | In | Type | Notes |
|---|
attachmentrequired | path | string (uuid) | |
signaturerequired | query | string | |
expiresrequired | query | string | |
variant | query | "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
| Name | In | Type | Notes |
|---|
threadrequired | path | string (uuid) | |
Responses
- 503
widget_disabled (feedback is paused for the project). Problem - 200 Whether the person now gets notifications for the thread.
| Field | Type | Notes |
|---|
watchingrequired | boolean | |
- 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
| Name | In | Type | Notes |
|---|
threadrequired | path | string (uuid) | |
Responses
- 503
widget_disabled (feedback is paused for the project). Problem - 200 Whether the person now gets notifications for the thread.
| Field | Type | Notes |
|---|
watchingrequired | boolean | |
- 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
| Name | In | Type | Notes |
|---|
cursor | query | string | next_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
| Name | In | Type | Notes |
|---|
Idempotency-Key | header | string | Any 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)
| Field | Type | Notes |
|---|
ids | (string (uuid))[] | |
thread_id | string (uuid) | |
all | boolean | |
Responses
- 503
widget_disabled (feedback is paused for the project). Problem - 200 How many were marked and what is still unread.
| Field | Type | Notes |
|---|
markedrequired | integer | |
unreadrequired | integer | |
- 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)
| Field | Type | Notes |
|---|
socket_idrequired | string | |
channel_namerequired | string | |
Responses
- 503
widget_disabled (feedback is paused for the project). Problem - 200 Channel signature.
| Field | Type | Notes |
|---|
authrequired | string | |
channel_data | string | JSON-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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
Responses
- 200 The project.
| Field | Type | Notes |
|---|
datarequired | 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 - 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
Responses
- 200 The tokens, without their secrets.
- 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
Request body (application/json)
| Field | Type | Notes |
|---|
namerequired | string | |
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. |
days | integer | |
Responses
- 201 Created. The secret is shown only here.
- 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
tokenrequired | path | integer | |
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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
kind | query | "feedback" | "task" | "all" | Website feedback, dashboard tasks, or both (default). |
page_url | query | string | |
status | query | string | |
branch | query | string | |
environment | query | Environment | |
assignee | query | string | Up to 10 person ids (or me) comma-separated, or none alone. |
q | query | string | |
label | query | string | Only threads with any of these labels: comma-separated label ids or names (names ignore case). |
sort | query | "recent" | "oldest" | "priority" | "number" | |
limit | query | integer | |
cursor | query | string | next_cursor from the previous page. |
If-None-Match | header | string | The 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.| Field | Type | Notes |
|---|
datarequired | AgentThread[] | |
next_cursorrequired | string | 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
threadrequired | path | string | The 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
threadrequired | path | string | The thread id, or its reference such as CK-12 (or just 12). |
limit | query | integer | |
cursor | query | string | next_cursor from the previous page. |
If-None-Match | header | string | The 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
threadrequired | path | string | The thread id, or its reference such as CK-12 (or just 12). |
Idempotency-Key | header | string | Any 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)
| Field | Type | Notes |
|---|
bodyrequired | Body | |
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}. - 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
threadrequired | path | string | The thread id, or its reference such as CK-12 (or just 12). |
Idempotency-Key | header | string | Any 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)
| Field | Type | Notes |
|---|
note | string | 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}. - 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
threadrequired | path | string | The thread id, or its reference such as CK-12 (or just 12). |
Idempotency-Key | header | string | Any 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)
| Field | Type | Notes |
|---|
url | string | null | Commit, pull request or other https URL. Required without sha. |
sha | string | null | |
title | string | null | |
Responses
- 201 Linked (adding the same link again returns it).
- 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}. - 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
Idempotency-Key | header | string | Any 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)
| Field | Type | Notes |
|---|
titlerequired | string | |
description | string | null | |
priority | "none" | "low" | "medium" | "high" | "urgent" | null | |
Responses
- 201 The new task.
- 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
Idempotency-Key | header | string | Any 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)
| Field | Type | Notes |
|---|
urlrequired | string | |
eventsrequired | ("task.created" | "task.updated" | "task.status_changed" | "task.deleted" | "task.restored" | "comment.created")[] | |
description | string | null | |
Responses
- 201 Subscribed.
| Field | Type | Notes |
|---|
idrequired | string | |
urlrequired | string | |
eventsrequired | string[] | |
secretrequired | string | Signing 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
hookrequired | path | string | |
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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
agentRequestrequired | path | string (uuid) | |
Responses
- 200 The request.
- 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
| Name | In | Type | Notes |
|---|
projectrequired | path | string | |
threadrequired | path | string | The thread id, or its reference such as CK-12 (or just 12). |
size | query | "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)
| Field | Type | Notes |
|---|
grant_typerequired | "authorization_code" | "refresh_token" | |
code | string | |
code_verifier | string | |
refresh_token | string | |
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)
| Field | Type | Notes |
|---|
refresh_token | string | |
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.
| Field | Type | Notes |
|---|
personrequired | object | |
datarequired | object[] | |
- 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)
| Field | Type | Notes |
|---|
project_keyrequired | string | |
originrequired | string | |
code_challengerequired | string | |
code_challenge_methodrequired | "S256" | |
Responses
- 200 A one-time widget code.
| Field | Type | Notes |
|---|
coderequired | string | |
expires_inrequired | integer | |
- 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
| Field | Type | Notes |
|---|
idrequired | string (uuid) | |
actionrequired | "reply" | "ready_for_review" | "link" | |
statusrequired | "pending" | "approved" | "declined" | "expired" | |
thread_idrequired | string (uuid) | |
decline_reasonrequired | string | null | |
resultrequired | object | null | |
created_atrequired | NullableDateTime | |
decided_atrequired | NullableDateTime | |
AgentThread
A thread as agents see it: website feedback or a dashboard task. Tasks have no page URL, pin or first message.
| Field | Type | Notes |
|---|
idrequired | string (uuid) | |
numberrequired | integer | |
referencerequired | string | Human 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. |
title | string | Short title. For website feedback it starts as the first line of the comment; the team can edit it. |
description | string | null | Text of a dashboard task (tasks only; feedback text is in first_message). |
links | ThreadLink[] | |
statusrequired | Status | |
priorityrequired | Priority | |
assigneerequired | Person | null | |
due_atrequired | DueDate | |
labels | Label[] | |
pagerequired | object | Where the feedback was left. url and path are null for dashboard tasks. |
anchorrequired | Anchor | null | Where on the page the pin is. Null for dashboard tasks. |
style_changes | StyleChange[] | Style changes the reviewer suggested for the anchored element. Empty when none. |
breakpointrequired | integer | null | |
authorrequired | Person | |
message_countrequired | integer | |
first_message | Message | null | The feedback comment. Null for dashboard tasks (their text is in description). |
capture | object | null | |
last_activity_atrequired | DateTime | |
resolved_atrequired | NullableDateTime | |
closed_atrequired | NullableDateTime | |
created_atrequired | DateTime | |
updated_atrequired | DateTime | |
pending_verification | boolean | True 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
AgentToken
Anchor
Where the feedback is pinned. Several strategies; the widget re-anchors with the best match.
| Field | Type | Notes |
|---|
typerequired | "element" | "region" | "text" | "page" | |
selector | string | null | |
xpath | string | null | |
source | string | null | data-ck-src (file:line:col) from a build plugin. Path characters only: no spaces, quotes, backticks or line breaks. |
ck_id | string | null | The element's data-ck-id. Letters, numbers and _ - . : / only. |
fingerprint | string | null | |
component | string | null | Component name read from a dev build (React, Vue, Svelte, Preact) when no build plugin is installed. One line. |
shadow_path | string[] | null | Selectors of the open shadow hosts around the element, outermost first. |
frame | string | null | Selector of the same-origin iframe that holds the element. |
frames | string[] | null | Nested 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. |
quote | string | null | Required for text anchors. |
offset | object | null | |
rect | object | null | Required for region anchors. |
pagerequired | object | |
Attachment
| Field | Type | Notes |
|---|
idrequired | string (uuid) | |
kindrequired | "screenshot" | "file" | |
namerequired | string | |
mimerequired | string | |
bytesrequired | integer | |
widthrequired | integer | null | |
heightrequired | integer | null | |
statusrequired | "pending" | "processing" | "ready" | "rejected" | |
urlrequired | string | null | Signed download URL (15 minutes); only when ready. |
thumbnail_urlrequired | string | null | |
AttachmentEnvelope
Body
Markdown-lite text. Rendered as text, never HTML.
string
CaptureInput
ChecklistItem
| Field | Type | Notes |
|---|
idrequired | string (uuid) | |
bodyrequired | string | |
donerequired | boolean | |
ChecklistItemEnvelope
ConsoleEntry
| Field | Type | Notes |
|---|
levelrequired | "error" | "warn" | |
messagerequired | string | |
at | string | null | |
CreateThread
| Field | Type | Notes |
|---|
pagerequired | object | |
breakpoint | integer | null | Viewport width in CSS pixels. |
bodyrequired | Body | |
anchorrequired | Anchor | |
capture | CaptureInput | null | |
attachment_ids | (string (uuid))[] | |
mention_ids | integer[] | |
label_ids | integer[] | Workspace members only. |
priority | Priority | Workspace members only. |
assignee_id | integer | null | Workspace members only. |
assignee_ids | integer[] | Everyone assigned, the first being the primary (assignee_id). Workspace members only. |
description | string | null | Extra detail under the comment. |
checklist | string[] | Checklist items to start with. |
style_changes | StyleChange[] | null | Style changes suggested for the anchored element (the widget's "Adjust style" panel). Each property at most once. |
DateTime
string (date-time)
Device
| Field | Type | Notes |
|---|
browser | string | null | |
browser_version | string | null | |
os | string | null | |
os_version | string | null | |
type | "desktop" | "tablet" | "mobile" | null | |
locale | string | null | |
color_scheme | "light" | "dark" | null | |
user_agent | string | null | Shown 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.
| Field | Type | Notes |
|---|
typerequired | string | The (latest) change, for example thread.created, thread.updated, thread.deleted, thread.restored, message.created, message.updated, message.deleted, reaction.changed. |
thread_idrequired | string | The thread that changed. |
message_idrequired | string | null | |
event_idrequired | string | Unique per change (the latest one for a coalesced ping). |
actorrequired | string | null | user:{id} or guest:{id} of whoever made the change; null on a coalesced ping. Kept for older widgets; prefer by. |
page_pathrequired | string | null | Path 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. |
byrequired | string | null | Opaque 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. |
coalescedrequired | boolean | True when this ping stands for several changes within one second. |
IdempotencyKeyReused
| Field | Type | Notes |
|---|
typerequired | string (uri) | |
titlerequired | string | |
statusrequired | integer | |
coderequired | "idempotency_key_reused" | |
detail | string | |
instance | string | |
errors | object | |
Inbox
| Field | Type | Notes |
|---|
unreadrequired | integer | |
next_cursorrequired | string | null | Older notifications: pass as cursor. Null on the last page. |
datarequired | object[] | |
Label
| Field | Type | Notes |
|---|
idrequired | integer | |
namerequired | string | |
colorrequired | string | |
MarkupShape
| Field | Type | Notes |
|---|
toolrequired | "arrow" | "rect" | "pen" | "blur" | "text" | "line" | "ellipse" | |
width | integer | null | Stroke width in screenshot pixels. |
color | string | null | |
points | number[] | null | |
text | string | null | |
Message
| Field | Type | Notes |
|---|
idrequired | string (uuid) | |
thread_idrequired | string (uuid) | |
authorrequired | Person | |
bodyrequired | string | |
sourcerequired | "widget" | "dashboard" | "agent" | "email" | "integration" | "migrated" | |
reply_to | object | null | |
attachments | Attachment[] | |
reactions | object[] | |
etag | string | Send as If-Match when editing this message. |
edited_atrequired | NullableDateTime | |
created_atrequired | DateTime | |
MessageEnvelope
MessagePage
| Field | Type | Notes |
|---|
datarequired | Message[] | |
next_cursorrequired | string | null | |
NetworkEntry
| Field | Type | Notes |
|---|
methodrequired | string | |
urlrequired | string | |
statusrequired | integer | |
duration_ms | integer | null | |
NullableDateTime
string (date-time) | null
PendingGuestToken
| Field | Type | Notes |
|---|
token_typerequired | "Bearer" | |
access_tokenrequired | string | Pending token: only for POST /threads (once), uploads, /guest/verify and /guest/resend. |
expires_inrequired | integer | |
pending_sessionrequired | object | |
Person
Public shape of a person. Never includes an email address.
| Field | Type | Notes |
|---|
typerequired | "user" | "guest" | "agent" | "integration" | "system" | "deleted" | |
idrequired | string | null | |
namerequired | string | |
avatar_urlrequired | string | null | |
Priority
"none" | "low" | "medium" | "high" | "urgent"
Problem
| Field | Type | Notes |
|---|
typerequired | string (uri) | |
titlerequired | string | |
statusrequired | integer | |
coderequired | string | Stable 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. |
detail | string | |
instance | string | |
Session
| Field | Type | Notes |
|---|
personrequired | Person | |
rolerequired | "admin" | "member" | "reviewer" | "guest" | |
email | string | null | Your own address, for Settings. |
dashboard_url | string | null | This project in the dashboard; team members only. |
repository | string | null | The linked GitHub repository as owner/name; team members only. Used to open coding agents in the right clone. |
realtime_keyrequired | string | Opaque key that feedback.changed pings carry in by when this person made the change (see FeedbackChangedPing). It differs per project; do not parse it. |
projectrequired | object | |
environmentrequired | Environment | |
capabilitiesrequired | object | What 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().
| Field | Type | Notes |
|---|
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" | |
from | string | null | The value on the page when it was suggested. |
torequired | string | null | The suggested value; null means remove (unset) the value. |
Thread
| Field | Type | Notes |
|---|
idrequired | string (uuid) | |
numberrequired | integer | |
referencerequired | string | Human reference, unique per project. |
kind | "feedback" | Always feedback here: the widget never sees tasks made in the dashboard. |
title | string | Short title. For website feedback it starts as the first line of the comment; the team can edit it. |
description | string | null | Extra detail under the comment (the comment itself is first_message). |
assignees | Person[] | Everyone assigned, the primary (assignee) first. |
checklist | ChecklistItem[] | |
links | ThreadLink[] | |
statusrequired | Status | |
priorityrequired | Priority | |
assigneerequired | Person | null | |
due_atrequired | DueDate | |
labels | Label[] | |
pagerequired | object | |
anchorrequired | Anchor | |
style_changes | StyleChange[] | Style changes the reviewer suggested for the anchored element. Empty when none. |
breakpointrequired | integer | null | |
authorrequired | Person | |
message_countrequired | integer | |
first_message | Message | |
capture | object | null | |
last_activity_atrequired | DateTime | |
resolved_atrequired | NullableDateTime | |
closed_atrequired | NullableDateTime | |
created_atrequired | DateTime | |
updated_atrequired | DateTime | |
pending_verification | boolean | True 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. |
watching | boolean | Whether you get emails about this thread (see PUT and DELETE /threads/{thread}/watch). Only on GET /threads/{thread}. |
ThreadEnvelope
| Field | Type | Notes |
|---|
datarequired | Thread | |
ThreadLink
| Field | Type | Notes |
|---|
idrequired | integer | |
kindrequired | "commit" | "pull_request" | "link" | |
urlrequired | string | null | |
titlerequired | string | null | |
sharequired | string | null | |
sourcerequired | "agent" | "cli" | "github" | "dashboard" | "integration" | |
created_atrequired | NullableDateTime | |
TokenPair
| Field | Type | Notes |
|---|
token_typerequired | "Bearer" | |
access_tokenrequired | string | |
expires_inrequired | integer | Seconds (15 minutes). |
refresh_tokenrequired | string | Rotates on every use. |
UpdateThread
| Field | Type | Notes |
|---|
status | Status | |
priority | Priority | |
assignee_id | integer | null | null unassigns everyone. |
assignee_ids | integer[] | The whole list of assignees, the first being the primary. |
description | string | null | The author or the team. |
due_at | string (date) | null | A calendar day, YYYY-MM-DD; null clears it. A date-time from older clients counts as the day it names. |
label_ids | integer[] | |
anchor | Anchor | |
style_changes | StyleChange[] | null | The 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
| Field | Type | Notes |
|---|
idrequired | string (uuid) | |
uploadrequired | object | |
max_bytesrequired | integer | |
ValidationProblem
| Field | Type | Notes |
|---|
typerequired | string (uri) | |
titlerequired | string | |
statusrequired | integer | |
coderequired | "validation_failed" | |
detail | string | |
instance | string | |
errorsrequired | object | |
Viewport
| Field | Type | Notes |
|---|
widthrequired | integer | |
heightrequired | integer | |
dpr | number | null | |
zoom | number | null | |
WebhookActor
| Field | Type | Notes |
|---|
typerequired | "user" | "guest" | "agent" | "integration" | "system" | |
namerequired | string | null | |
WebhookEnvelope
Every webhook is a JSON POST with this envelope. Payloads are versioned by api_version.
| Field | Type | Notes |
|---|
idrequired | string (uuid) | The same for every retry and replay of one event (also the CommentKit-Event-Id header). |
typerequired | string | |
api_versionrequired | "2026-10" | |
created_atrequired | string (date-time) | |
projectrequired | object | |
datarequired | object | |
WebhookPingData
| Field | Type | Notes |
|---|
messagerequired | string | |
WebhookStatusChangedData
WebhookTask
| Field | Type | Notes |
|---|
idrequired | string (uuid) | |
referencerequired | string | |
kindrequired | "feedback" | "task" | |
titlerequired | string | |
statusrequired | "open" | "in_progress" | "in_review" | "resolved" | "closed" | |
priorityrequired | "none" | "low" | "medium" | "high" | "urgent" | |
assigneerequired | object | null | |
due_atrequired | DueDate | |
labelsrequired | string[] | |
page_urlrequired | string | null | |
urlrequired | string | null | |
created_atrequired | string (date-time) | null | |
updated_atrequired | string (date-time) | null | |
WebhookTaskData
WebhookTaskDeletedData
WebhookTaskUpdatedData
| Field | Type | Notes |
|---|
taskrequired | WebhookTask | |
changesrequired | object | One 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. |
actorrequired | WebhookActor | |
WidgetConfig
| Field | Type | Notes |
|---|
projectrequired | object | |
enabledrequired | boolean | false when the kill switch is on. |
read_onlyrequired | boolean | True when new feedback is turned off or the plan ended. |
environmentrequired | Environment | |
local_only_for_membersrequired | boolean | True on localhost that is not an allowed website. |
featuresrequired | object | |
appearancerequired | object | |
privacyrequired | object | |
limitsrequired | object | |
authrequired | object | |
realtimerequired | null | object | Socket settings when live updates are on; null otherwise. |
reporting | object | Only 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. |