Skip to Content

Threads

Threads are the comment pins placed on a markup. Use these endpoints to list, read, create, resolve, reopen, and delete threads, reposition a thread’s pin, and manage the screenshot attached to a thread. Authenticate with an exchange token (embedded feedback) or a workspace- or organization-scoped API key (server-to-server) — see the Authentication overview.

List threads

GET /api/v2/threads

List the threads on a markup. Filter and paginate with the query parameters.

GET /api/v2/threads
curl "https://api.markup.io/api/v2/threads?markupId=9d3c0b50-01ed-49a0-a07a-fdfdb77be9d8" \ -X GET \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "threads": [ {} ], "query": { "resolved": true, "viewMode": null }, "offset": 123, "total": 123, "allTotals": null, "cursorId": "some cursorId" } }

Response Body ProjectThreadsResponsePublic

OptionType
threads
query
offset number
total number
allTotals ThreadTotals | null
cursorId string

Request Query Params ListThreadsQuery

OptionType
markupId string
cursorId optionalstring
limit optionalnumber
withThreadOrMessageId optionalstring
resolved optionalboolean
order optional
viewMode optional
q optionalstring
offset optionalnumber

Get thread

GET /api/v2/threads/:id

Get a single thread by ID, including its messages.

GET /api/v2/threads/:id
curl "https://api.markup.io/api/v2/threads/d32f1a89-f681-466c-99e5-46441a6a97a0" \ -X GET \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "scopes": [ "some string" ], "assignedRoleSlugs": [ "some string" ], "user": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "name": "some name", "email": null, "guest": true, "profileImage": "some profileImage", "avatarUrl": null, "hubspotId": null, "oktaUserId": null }, "number": 123, "resolved": true, "messages": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "scopes": [ "some string" ], "assignedRoleSlugs": [ "some string" ], "threadId": "some threadId", "message": "some message", "messageJson": {}, "user": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "name": "some name", "email": null, "guest": true, "profileImage": "some profileImage", "avatarUrl": null, "hubspotId": null, "oktaUserId": null }, "userMetaData": {}, "attachments": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "messageId": "some messageId", "url": "some url", "etag": "some etag", "mimeType": "some mimeType", "originalFilename": "some originalFilename", "filesize": 123, "width": null, "height": null } ], "loomVideos": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "messageId": "some messageId", "title": "some title", "sharedUrl": "some sharedUrl", "width": 123, "height": 123 } ], "priority": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "name": "some name", "level": 123, "color": null }, "read": true } ], "userMetaData": {}, "priority": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "name": "some name", "level": 123, "color": null }, "projectId": "some projectId", "proxyUrl": "some proxyUrl", "originalUrl": "some originalUrl", "canonicalUrl": "some canonicalUrl", "group": null, "elements": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "threadId": "some threadId", "path": "some path", "offsetXPercentage": 123, "offsetYPercentage": 123 } ], "parents": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "path": "some path", "depth": 123, "proxyUrl": "some proxyUrl", "originalUrl": "some originalUrl", "canonicalUrl": "some canonicalUrl" } ], "viewModeId": "some viewModeId", "screenshot": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "url": null, "etag": null, "mimeType": "some mimeType", "width": 123, "height": 123, "filesize": 123, "type": "extension", "isProcessing": true }, "viewportXPercentage": null, "viewportYPercentage": null, "textHighlight": { "startKey": "some startKey", "endKey": "some endKey", "startOffset": 123, "startIndex": 123, "endOffset": 123, "endIndex": 123, "text": "some text" }, "read": true, "unreadMessageCount": 123 } }

Response Body - ThreadResponsePublic

Request Path Params IdRequestParam

OptionType
id string

Create thread

POST /api/v2/threads

Create a new thread (comment pin) on a markup.

POST /api/v2/threads
curl "https://api.markup.io/api/v2/threads" \ -X POST \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json" \ --data '{ "isImageThread": true, "url": "some url", "canonicalUrl": "some canonicalUrl", "elements": [ { "elementPath": "some elementPath", "offsetXPercentage": 123, "offsetYPercentage": 123 } ], "offsetXPercentage": 123, "offsetYPercentage": 123, "message": {}, "browserContext": {}, "elementParents": [ { "url": "some url", "canonicalUrl": "some canonicalUrl", "path": "some path", "depth": 123 } ], "viewModeId": "f6e395ec-45ba-4872-9697-0faf31741a09", "viewportXPercentage": null, "viewportYPercentage": null }'
200 OK
{ "data": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "scopes": [ "some string" ], "assignedRoleSlugs": [ "some string" ], "user": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "name": "some name", "email": null, "guest": true, "profileImage": "some profileImage", "avatarUrl": null, "hubspotId": null, "oktaUserId": null }, "number": 123, "resolved": true, "messages": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "scopes": [ "some string" ], "assignedRoleSlugs": [ "some string" ], "threadId": "some threadId", "message": "some message", "messageJson": {}, "user": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "name": "some name", "email": null, "guest": true, "profileImage": "some profileImage", "avatarUrl": null, "hubspotId": null, "oktaUserId": null }, "userMetaData": {}, "attachments": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "messageId": "some messageId", "url": "some url", "etag": "some etag", "mimeType": "some mimeType", "originalFilename": "some originalFilename", "filesize": 123, "width": null, "height": null } ], "loomVideos": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "messageId": "some messageId", "title": "some title", "sharedUrl": "some sharedUrl", "width": 123, "height": 123 } ], "priority": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "name": "some name", "level": 123, "color": null }, "read": true } ], "userMetaData": {}, "priority": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "name": "some name", "level": 123, "color": null }, "projectId": "some projectId", "proxyUrl": "some proxyUrl", "originalUrl": "some originalUrl", "canonicalUrl": "some canonicalUrl", "group": null, "elements": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "threadId": "some threadId", "path": "some path", "offsetXPercentage": 123, "offsetYPercentage": 123 } ], "parents": [ { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "path": "some path", "depth": 123, "proxyUrl": "some proxyUrl", "originalUrl": "some originalUrl", "canonicalUrl": "some canonicalUrl" } ], "viewModeId": "some viewModeId", "screenshot": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "url": null, "etag": null, "mimeType": "some mimeType", "width": 123, "height": 123, "filesize": 123, "type": "extension", "isProcessing": true }, "viewportXPercentage": null, "viewportYPercentage": null, "textHighlight": { "startKey": "some startKey", "endKey": "some endKey", "startOffset": 123, "startIndex": 123, "endOffset": 123, "endIndex": 123, "text": "some text" }, "read": true, "unreadMessageCount": 123 } }

Response Body - ThreadResponsePublic

Request Body CreateThreadRequest

OptionType
isImageThread boolean
projectId optionalstring
projectImageId optionalstring
url string
canonicalUrl string
group optionalstring

Opaque page group chosen by the host app, 1 to 255 characters, case-sensitive. Fixed at creation. Webpage threads only.

elements
offsetXPercentage number
offsetYPercentage number
startTime optionalnumber
endTime optionalnumber
message Delta
browserContext
elementParents
viewModeId string
viewportXPercentage number | null
viewportYPercentage number | null
attachments optional
loomVideos optional
width optionalnumber
height optionalnumber
annotations optional
textHighlight optional
pageNumber optionalnumber
requireDimensionsOverride optionalboolean
priority optionalnumber

Resolve thread

POST /api/v2/threads/:id/resolve

Mark a thread as resolved.

POST /api/v2/threads/:id/resolve
curl "https://api.markup.io/api/v2/threads/078ccb46-8568-4142-ace1-2328f667c3f5/resolve" \ -X POST \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "threadId": "some threadId", "modifiedAt": 123 } }

Response Body ThreadResolvedResponse

OptionType
threadId string
modifiedAt number

Request Path Params IdRequestParam

OptionType
id string

Unresolve thread

POST /api/v2/threads/:id/unresolve

Reopen a previously resolved thread.

POST /api/v2/threads/:id/unresolve
curl "https://api.markup.io/api/v2/threads/37adaa81-2651-4303-b223-7582ab360cb6/unresolve" \ -X POST \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "threadId": "some threadId" } }

Response Body ThreadUnresolvedResponse

OptionType
threadId string

Request Path Params IdRequestParam

OptionType
id string

Mark thread as read

POST /api/v2/threads/:id/read

Mark a thread, and every message inside it, as read for the authenticated user. Read state is per user: with an exchange token it is recorded against the end user the token identifies, and with an API key it is recorded against the key’s API user. Returns 204 with no body.

POST /api/v2/threads/:id/read
curl "https://api.markup.io/api/v2/threads/fe08fef4-6cee-4d2e-b540-e483d9e78992/read" \ -X POST \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
Response
204 No Content

Request Path Params IdRequestParam

OptionType
id string

Delete thread

DELETE /api/v2/threads/:id

Delete a thread and all of its messages.

DELETE /api/v2/threads/:id
curl "https://api.markup.io/api/v2/threads/80ecf1b1-5077-44ca-a3ee-68e099bb2939" \ -X DELETE \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
Response
204 No Content

Request Path Params IdRequestParam

OptionType
id string

Move thread pin

PATCH /api/v2/threads/:id/move-pin

Reposition a thread’s pin on the markup.

PATCH /api/v2/threads/:id/move-pin
curl "https://api.markup.io/api/v2/threads/88f7fa85-1009-47ec-97fe-f4d4e7c879cd/move-pin" \ -X PATCH \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json" \ --data '{ "url": "some url", "canonicalUrl": "some canonicalUrl", "elements": [ { "elementPath": "some elementPath", "offsetXPercentage": 123, "offsetYPercentage": 123 } ], "browserContext": {}, "elementParents": [ { "url": "some url", "canonicalUrl": "some canonicalUrl", "path": "some path", "depth": 123 } ], "viewportXPercentage": 123, "viewportYPercentage": 123 }'
200 OK
{ "data": { "id": "some id", "userId": "some userId", "projectId": "some projectId", "number": 123, "lastActivityAt": null, "resolved": true, "screenshotId": null, "priorityId": null, "proxyHostId": "some proxyHostId", "urlPath": "some urlPath", "canonicalUrl": "some canonicalUrl", "group": null, "browserContext": {}, "viewModeId": "some viewModeId", "viewportXPercentage": null, "viewportYPercentage": null, "textHighlightId": null } }

Response Body - Thread

Request Path Params IdRequestParam

OptionType
id string

Request Body MoveThreadPinBody

OptionType
url string
canonicalUrl string
elements
browserContext
elementParents
viewportXPercentage number
viewportYPercentage number

Get screenshot upload policy

GET /api/v2/threads/:id/screenshot-upload-policy

Get a pre-signed S3 upload policy for attaching a screenshot to a thread. Upload the file to S3 with the returned policy, then call PUT /threads/:id/screenshot to attach it.

GET /api/v2/threads/:id/screenshot-upload-policy
curl "https://api.markup.io/api/v2/threads/23caf6fb-cd11-498d-a079-b3c2584a1c0f/screenshot-upload-policy?fileId=656c8688-5fdb-4ab3-a38d-689bcc9e7ea3&filesize=123&filename=some+filename&mimeType=some+mimeType" \ -X GET \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "key": "some key", "policy": {}, "bucket": "some bucket" } }

Response Body S3UploadPolicyResponse

OptionType
key string
policy PresignedPost
bucket string
metadata Record

Request Path Params IdRequestParam

OptionType
id string

Request Query Params ScreenshotUploadPolicyRequestQuery

OptionType
fileId string
filesize number
filename string
mimeType string

Set thread screenshot

PUT /api/v2/threads/:id/screenshot

Attach (or replace) the screenshot for a thread after uploading the file with the upload policy.

PUT /api/v2/threads/:id/screenshot
curl "https://api.markup.io/api/v2/threads/0d8d15cb-4038-43dd-b1ee-f3b659d94267/screenshot" \ -X PUT \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json" \ --data '{ "key": "some key", "etag": "some etag", "mimeType": "some mimeType", "width": 123, "height": 123, "filesize": 123, "fileId": "812b148b-e20a-45b0-bbac-ddb8df6e664b" }'
200 OK
{ "data": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "url": null, "etag": null, "mimeType": "some mimeType", "width": 123, "height": 123, "filesize": 123, "type": "extension", "isProcessing": true } }

Response Body - ScreenshotResponse

Request Path Params IdRequestParam

OptionType
id string

Request Body CreateScreenshotRequest

OptionType
key string
etag string
mimeType string
width number
height number
filesize number
originalFilename optionalstring
fileId string

Get thread screenshot

GET /api/v2/threads/:id/screenshot

Get the screenshot attached to a thread.

GET /api/v2/threads/:id/screenshot
curl "https://api.markup.io/api/v2/threads/1c99eed4-cbf5-40c6-a099-cb1933ac8644/screenshot" \ -X GET \ -H "Authorization: Bearer <API-KEY-SECRET>" \ -H "Markup-API-Version: 2023-02-22" \ -H "Content-Type: application/json"
200 OK
{ "data": { "id": "some id", "createdAt": {}, "modifiedAt": {}, "deletedAt": null, "url": null, "etag": null, "mimeType": "some mimeType", "width": 123, "height": 123, "filesize": 123, "type": "extension", "isProcessing": true } }

Response Body - ScreenshotResponse

Request Path Params IdRequestParam

OptionType
id string

ScreenshotResponse

OptionType
id string
createdAt Unix_Timestamp_Utc_Milliseconds
modifiedAt Unix_Timestamp_Utc_Milliseconds
deletedAt Unix_Timestamp_Utc_Milliseconds | null
url string | null
etag string | null
mimeType string
width number
height number
filesize number
type
isProcessing boolean

ThreadOrder

OptionType
ThreadOrder `page` | `pin_asc` | `pin_desc` | `activity` | `timestamp` | `priority_asc` | `priority_desc`

ViewModeCategory

OptionType
ViewModeCategory `desktop` | `tablet` | `mobile`

CreateThreadRequestElement

OptionType
elementPath string
offsetXPercentage number
offsetYPercentage number

BrowserContextRequest

OptionType
browserName string
browserVersion string
os string
osVersion string
osVersionName string
platform string
screenWidth number
screenHeight number
viewportWidth number
viewportHeight number

ThreadElementParentRequest

OptionType
url string
canonicalUrl string
path string
depth number

MessageRequestAttachment

OptionType
fileId string
key string
etag string
mimeType string
filesize number
originalFilename string
width number
height number
requireDimensionsOverride boolean
pdfAsImage boolean

LoomVideoRequest

OptionType
id string
title string
providerUrl string
embedUrl string
sharedUrl string
width number
height number

CanvasThreadAnnotationRequest

OptionType
version string
objects ThreadAnnotationObjectRequest[] | null
scale number
originalWidth number
originalHeight number

ThreadTextHighlightRequest

OptionType
startKey string
endKey string
startOffset number
startIndex number
endOffset number
endIndex number
text string

IProjectThreadQuery

OptionType
resolved boolean
viewMode

ThreadResponsePublic

WebpageThreadResponsePublic

OptionType
id string
createdAt Unix_Timestamp_Utc_Milliseconds
modifiedAt Unix_Timestamp_Utc_Milliseconds
deletedAt Unix_Timestamp_Utc_Milliseconds | null
scopes string[]
assignedRoleSlugs string[]
user AuthorUserResponse
number number
resolved boolean
messages MessageResponseWithReadState[]

The thread's messages, each carrying its own read flag for the caller.

userMetaData IUserResourceMetaData
lastActivityAt number
annotations AnnotationsResponse
priority PriorityResponse | null
projectId string
proxyUrl string
originalUrl string
canonicalUrl string
group string | null
elements ThreadElementResponse[]
parents ThreadElementParentResponse[]
browserContext IBrowserContext
viewModeId string
screenshot
viewportXPercentage number | null
viewportYPercentage number | null
textHighlight TextHighlightResponse | null
read boolean

True when the thread and every message in it have been seen by the caller. Mark it read with POST /api/v2/threads/:id/read. There is no historical baseline: anything the caller has not marked read is reported unread, including threads that predate their first call.

unreadMessageCount number

Number of messages in the thread the caller has not seen since they last changed.

ImageThreadResponsePublic

OptionType
id string
createdAt Unix_Timestamp_Utc_Milliseconds
modifiedAt Unix_Timestamp_Utc_Milliseconds
deletedAt Unix_Timestamp_Utc_Milliseconds | null
scopes string[]
assignedRoleSlugs string[]
user AuthorUserResponse
number number
resolved boolean
messages MessageResponseWithReadState[]

The thread's messages, each carrying its own read flag for the caller.

userMetaData IUserResourceMetaData
lastActivityAt number
annotations AnnotationsResponse
priority PriorityResponse | null
projectImageId string
offsetXPercentage number
offsetYPercentage number
startTime number
endTime number
read boolean

True when the thread and every message in it have been seen by the caller. Mark it read with POST /api/v2/threads/:id/read. There is no historical baseline: anything the caller has not marked read is reported unread, including threads that predate their first call.

unreadMessageCount number

Number of messages in the thread the caller has not seen since they last changed.

PdfThreadResponsePublic

OptionType
id string
createdAt Unix_Timestamp_Utc_Milliseconds
modifiedAt Unix_Timestamp_Utc_Milliseconds
deletedAt Unix_Timestamp_Utc_Milliseconds | null
scopes string[]
assignedRoleSlugs string[]
user AuthorUserResponse
number number
resolved boolean
messages MessageResponseWithReadState[]

The thread's messages, each carrying its own read flag for the caller.

userMetaData IUserResourceMetaData
lastActivityAt number
annotations AnnotationsResponse
priority PriorityResponse | null
projectImageId string
offsetXPercentage number
offsetYPercentage number
pageNumber number
textHighlight TextHighlightResponse
read boolean

True when the thread and every message in it have been seen by the caller. Mark it read with POST /api/v2/threads/:id/read. There is no historical baseline: anything the caller has not marked read is reported unread, including threads that predate their first call.

unreadMessageCount number

Number of messages in the thread the caller has not seen since they last changed.

Thread

OptionType
Thread

WebpageThread

OptionType
id string
userId string
projectId string
number number
lastActivityAt string | null
resolved boolean
screenshotId string | null
priorityId string | null
proxyHostId string
urlPath string
canonicalUrl string
group string | null
browserContext IBrowserContext
viewModeId string
viewportXPercentage number | null
viewportYPercentage number | null
textHighlightId string | null

ImageThread

OptionType
id string
userId string
projectId string
number number
lastActivityAt string | null
resolved boolean
screenshotId string | null
priorityId string | null
projectImageId string
offsetXPercentage number
offsetYPercentage number
startTime number
endTime number

PdfThread

OptionType
id string
userId string
projectId string
number number
lastActivityAt string | null
resolved boolean
screenshotId string | null
priorityId string | null
pageNumber number
pageRelativeX number
pageRelativeY number
textHighlightId string | null

ScreenshotType

OptionType
ScreenshotType `extension` | `native` | `static`